diff options
Diffstat (limited to 'source/blender/blenkernel')
373 files changed, 31892 insertions, 17887 deletions
diff --git a/source/blender/blenkernel/BKE_DerivedMesh.h b/source/blender/blenkernel/BKE_DerivedMesh.h index e3954e134da..1801c1ee1c9 100644 --- a/source/blender/blenkernel/BKE_DerivedMesh.h +++ b/source/blender/blenkernel/BKE_DerivedMesh.h @@ -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 @@ -251,8 +242,17 @@ struct DerivedMesh { void (*release)(DerivedMesh *dm); }; +/** + * Utility function to initialize a #DerivedMesh's function pointers to + * the default implementation (for those functions which have a default). + */ void DM_init_funcs(DerivedMesh *dm); +/** + * Utility function to initialize a #DerivedMesh for the desired number + * of vertices, edges and faces (doesn't allocate memory for them, just + * sets up the custom data layers)> + */ void DM_init(DerivedMesh *dm, DerivedMeshType type, int numVerts, @@ -261,6 +261,10 @@ void DM_init(DerivedMesh *dm, int numLoops, int numPolys); +/** + * Utility function to initialize a DerivedMesh for the desired number + * of vertices, edges and faces, with a layer setup copied from source + */ void DM_from_template_ex(DerivedMesh *dm, DerivedMesh *source, DerivedMeshType type, @@ -285,43 +289,59 @@ void DM_from_template(DerivedMesh *dm, */ bool DM_release(DerivedMesh *dm); +/** + * set the #CD_FLAG_NOCOPY flag in custom data layers where the mask is + * zero for the layer type, so only layer types specified by the mask + * will be copied + */ void DM_set_only_copy(DerivedMesh *dm, const struct CustomData_MeshMasks *mask); -/* adds a vertex/edge/face custom data layer to a DerivedMesh, optionally +/* Adds a vertex/edge/face custom data layer to a DerivedMesh, optionally * backed by an external data array * alloctype defines how the layer is allocated or copied, and how it is - * freed, see BKE_customdata.h for the different options - */ + * freed, see BKE_customdata.h for the different options. */ + void DM_add_vert_layer(struct DerivedMesh *dm, int type, eCDAllocType alloctype, void *layer); void DM_add_edge_layer(struct DerivedMesh *dm, int type, eCDAllocType alloctype, void *layer); void DM_add_tessface_layer(struct DerivedMesh *dm, int type, eCDAllocType alloctype, void *layer); void DM_add_loop_layer(DerivedMesh *dm, int type, eCDAllocType alloctype, void *layer); void DM_add_poly_layer(struct DerivedMesh *dm, int type, eCDAllocType alloctype, void *layer); -/* custom data access functions - * return pointer to data from first layer which matches type - * if they return NULL for valid indices, data doesn't exist - * note these return pointers - any change modifies the internals of the mesh - */ +/* -------------------------------------------------------------------- */ +/** \name Custom Data Access Functions + * + * \return pointer to data from first layer which matches type + * if they return NULL for valid indices, data doesn't exist. + * \note these return pointers - any change modifies the internals of the mesh. + * \{ */ + void *DM_get_vert_data(struct DerivedMesh *dm, int index, int type); void *DM_get_edge_data(struct DerivedMesh *dm, int index, int type); void *DM_get_tessface_data(struct DerivedMesh *dm, int index, int type); void *DM_get_poly_data(struct DerivedMesh *dm, int index, int type); -/* custom data layer access functions - * return pointer to first data layer which matches type (a flat array) - * if they return NULL, data doesn't exist - * note these return pointers - any change modifies the internals of the mesh - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Custom Data Layer Access Functions + * + * \return pointer to first data layer which matches type (a flat array) + * if they return NULL, data doesn't exist. + * \note these return pointers - any change modifies the internals of the mesh. + * \{ */ + void *DM_get_vert_data_layer(struct DerivedMesh *dm, int type); void *DM_get_edge_data_layer(struct DerivedMesh *dm, int type); void *DM_get_tessface_data_layer(struct DerivedMesh *dm, int type); void *DM_get_poly_data_layer(struct DerivedMesh *dm, int type); void *DM_get_loop_data_layer(struct DerivedMesh *dm, int type); -/* custom data copy functions +/** \} */ + +/** + * Custom data copy functions * copy count elements from source_index in source to dest_index in dest - * these copy all layers for which the CD_FLAG_NOCOPY flag is not set + * these copy all layers for which the CD_FLAG_NOCOPY flag is not set. */ void DM_copy_vert_data(struct DerivedMesh *source, struct DerivedMesh *dest, @@ -329,13 +349,26 @@ void DM_copy_vert_data(struct DerivedMesh *source, int dest_index, int count); -/* Sets up mpolys for a DM based on face iterators in source. */ +/** + * Sets up mpolys for a DM based on face iterators in source. + */ void DM_DupPolys(DerivedMesh *source, DerivedMesh *target); void DM_ensure_normals(DerivedMesh *dm); +/** + * Ensure the array is large enough. + * + * \note This function must always be thread-protected by caller. + * It should only be used by internal code. + */ void DM_ensure_looptri_data(DerivedMesh *dm); +/** + * Interpolates vertex data from the vertices indexed by `src_indices` in the + * source mesh using the given weights and stores the result in the vertex + * indexed by `dest_index` in the `dest` mesh. + */ void DM_interp_vert_data(struct DerivedMesh *source, struct DerivedMesh *dest, int *src_indices, @@ -343,9 +376,11 @@ void DM_interp_vert_data(struct DerivedMesh *source, int count, int dest_index); -void mesh_get_mapped_verts_coords(struct Mesh *me_eval, float (*r_cos)[3], const int totcos); +void mesh_get_mapped_verts_coords(struct Mesh *me_eval, float (*r_cos)[3], int totcos); -/* same as above but won't use render settings */ +/** + * Same as above but won't use render settings. + */ struct Mesh *editbmesh_get_eval_cage(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *, @@ -355,12 +390,6 @@ struct Mesh *editbmesh_get_eval_cage_from_orig(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *obedit, const struct CustomData_MeshMasks *dataMask); -struct Mesh *editbmesh_get_eval_cage_and_final(struct Depsgraph *depsgraph, - struct Scene *scene, - struct Object *, - struct BMEditMesh *em, - const struct CustomData_MeshMasks *dataMask, - struct Mesh **r_final); float (*editbmesh_vert_coords_alloc(struct BMEditMesh *em, int *r_vert_len))[3]; bool editbmesh_modifier_is_enabled(struct Scene *scene, @@ -370,7 +399,6 @@ bool editbmesh_modifier_is_enabled(struct Scene *scene, void makeDerivedMesh(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, - struct BMEditMesh *em, const struct CustomData_MeshMasks *dataMask); void DM_calc_loop_tangents(DerivedMesh *dm, @@ -378,14 +406,6 @@ void DM_calc_loop_tangents(DerivedMesh *dm, const char (*tangent_names)[MAX_NAME], int tangent_names_len); -/* debug only */ -#ifndef NDEBUG -char *DM_debug_info(DerivedMesh *dm); -void DM_debug_print(DerivedMesh *dm); - -bool DM_is_valid(DerivedMesh *dm); -#endif - #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h index 9f69c5e3976..0b09bfd8730 100644 --- a/source/blender/blenkernel/BKE_action.h +++ b/source/blender/blenkernel/BKE_action.h @@ -79,70 +79,143 @@ typedef enum eAction_TransformFlags { ACT_TRANS_ALL = (ACT_TRANS_ONLY | ACT_TRANS_PROP), } eAction_TransformFlags; -/* Return flags indicating which transforms the given object/posechannel has +/** + * Return flags indicating which transforms the given object/posechannel has * - if 'curves' is provided, a list of links to these curves are also returned - * whose nodes WILL NEED FREEING + * whose nodes WILL NEED FREEING. */ short action_get_item_transforms(struct bAction *act, struct Object *ob, struct bPoseChannel *pchan, ListBase *curves); -/* Some kind of bounding box operation on the action */ +/** + * Calculate the extents of given action. + */ void calc_action_range(const struct bAction *act, float *start, float *end, short incl_modifiers); -/* Does action have any motion data at all? */ +/* Retrieve the intended playback frame range, using the manually set range if available, + * or falling back to scanning F-Curves for their first & last frames otherwise. */ +void BKE_action_get_frame_range(const struct bAction *act, float *r_start, float *r_end); + +/** + * Check if the given action has any keyframes. + */ bool action_has_motion(const struct bAction *act); +/** + * Is the action configured as cyclic. + */ +bool BKE_action_is_cyclic(const struct bAction *act); + /* Action Groups API ----------------- */ -/* Get the active action-group for an Action */ +/** + * Get the active action-group for an Action. + */ struct bActionGroup *get_active_actiongroup(struct bAction *act); -/* Make the given Action Group the active one */ +/** + * Make the given Action-Group the active one. + */ void set_active_action_group(struct bAction *act, struct bActionGroup *agrp, short select); -/* Sync colors used for action/bone group with theme settings */ +/** + * Sync colors used for action/bone group with theme settings. + */ void action_group_colors_sync(struct bActionGroup *grp, const struct bActionGroup *ref_grp); -/* Add a new action group with the given name to the action */ +/** + * Add a new action group with the given name to the action> + */ struct bActionGroup *action_groups_add_new(struct bAction *act, const char name[]); -/* Add given channel into (active) group */ +/** + * Add given channel into (active) group + * - assumes that channel is not linked to anything anymore + * - always adds at the end of the group + */ void action_groups_add_channel(struct bAction *act, struct bActionGroup *agrp, struct FCurve *fcurve); -/* Remove the given channel from all groups */ +/** + * Remove the given channel from all groups. + */ void action_groups_remove_channel(struct bAction *act, struct FCurve *fcu); -/* Reconstruct group channel pointers. */ +/** + * Reconstruct group channel pointers. + * 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(struct bAction *act); -/* Find a group with the given name */ +/** + * Find a group with the given name. + */ struct bActionGroup *BKE_action_group_find_name(struct bAction *act, const char name[]); -/* Clear all 'temp' flags on all groups */ +/** + * Clear all 'temp' flags on all groups. + */ void action_groups_clear_tempflags(struct bAction *act); +/** + * Return whether the action has one unique point in time keyed. + * + * This is mostly for the pose library, which will have different behavior depending on whether an + * Action corresponds to a "pose" (one keyframe) or "animation snippet" (multiple keyframes). + * + * \return `false` when there is no keyframe at all or keys on different points in time, `true` + * when exactly one point in time is keyed. + */ +bool BKE_action_has_single_frame(const struct bAction *act); + /* Pose API ----------------- */ void BKE_pose_channel_free(struct bPoseChannel *pchan); +/** + * Deallocates a pose channel. + * Does not free the pose channel itself. + */ void BKE_pose_channel_free_ex(struct bPoseChannel *pchan, bool do_id_user); +/** + * Clears the runtime cache of a pose channel without free. + */ void BKE_pose_channel_runtime_reset(struct bPoseChannel_Runtime *runtime); +/** + * Reset all non-persistent fields. + */ void BKE_pose_channel_runtime_reset_on_copy(struct bPoseChannel_Runtime *runtime); +/** + * Deallocates runtime cache of a pose channel + */ void BKE_pose_channel_runtime_free(struct bPoseChannel_Runtime *runtime); +/** + * Deallocates runtime cache of a pose channel's B-Bone shape. + */ void BKE_pose_channel_free_bbone_cache(struct bPoseChannel_Runtime *runtime); void BKE_pose_channels_free(struct bPose *pose); +/** + * Removes and deallocates all channels from a pose. + * Does not free the pose itself. + */ void BKE_pose_channels_free_ex(struct bPose *pose, bool do_id_user); +/** + * Removes the hash for quick lookup of channels, must be done when adding/removing channels. + */ void BKE_pose_channels_hash_ensure(struct bPose *pose); void BKE_pose_channels_hash_free(struct bPose *pose); +/** + * Selectively remove pose channels. + */ void BKE_pose_channels_remove(struct Object *ob, bool (*filter_fn)(const char *bone_name, void *user_data), void *user_data); @@ -150,18 +223,79 @@ void BKE_pose_channels_remove(struct Object *ob, void BKE_pose_free_data_ex(struct bPose *pose, bool do_id_user); void BKE_pose_free_data(struct bPose *pose); void BKE_pose_free(struct bPose *pose); +/** + * Removes and deallocates all data from a pose, and also frees the pose. + */ void BKE_pose_free_ex(struct bPose *pose, bool do_id_user); +/** + * Allocate a new pose on the heap, and copy the src pose and its channels + * into the new pose. *dst is set to the newly allocated structure, and assumed to be NULL. + * + * \param dst: Should be freed already, makes entire duplicate. + */ void BKE_pose_copy_data_ex(struct bPose **dst, const struct bPose *src, - const int flag, - const bool copy_constraints); -void BKE_pose_copy_data(struct bPose **dst, const struct bPose *src, const bool copy_constraints); + int flag, + bool copy_constraints); +void BKE_pose_copy_data(struct bPose **dst, const struct bPose *src, bool copy_constraints); +/** + * Copy the internal members of each pose channel including constraints + * and ID-Props, used when duplicating bones in edit-mode. + * (unlike copy_pose_channel_data which only does posing-related stuff). + * + * \note use when copying bones in edit-mode (on returned value from #BKE_pose_channel_ensure) + */ void BKE_pose_channel_copy_data(struct bPoseChannel *pchan, const struct bPoseChannel *pchan_from); void BKE_pose_channel_session_uuid_generate(struct bPoseChannel *pchan); +/** + * Return a pointer to the pose channel of the given name + * from this pose. + */ struct bPoseChannel *BKE_pose_channel_find_name(const struct bPose *pose, const char *name); -struct bPoseChannel *BKE_pose_channel_active(struct Object *ob); +/** + * Checks if the bone is on a visible armature layer + * + * \return true if on a visible layer, false otherwise. + */ +bool BKE_pose_is_layer_visible(const struct bArmature *arm, const struct bPoseChannel *pchan); +/** + * Find the active pose-channel for an object + * + * \param check_arm_layer: checks if the bone is on a visible armature layer (this might be skipped + * (e.g. for "Show Active" from the Outliner). + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. + */ +struct bPoseChannel *BKE_pose_channel_active(struct Object *ob, bool check_arm_layer); +/** + * Find the active pose-channel for an object if it is on a visible armature layer + * (calls #BKE_pose_channel_active with check_arm_layer set to true) + * + * \return #bPoseChannel if found or NULL. + * \note #Object, not #bPose is used here, as we need info (layer/active bone) from Armature. + */ +struct bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob); +/** + * Use this when detecting the "other selected bone", + * when we have multiple armatures in pose mode. + * + * In this case the active-selected is an obvious choice when finding the target for a + * constraint for eg. however from the users perspective the active pose bone of the + * active object is the _real_ active bone, so any other non-active selected bone + * is a candidate for being the other selected bone, see: T58447. + */ struct bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob); +/** + * Looks to see if the channel with the given name already exists + * in this pose - if not a new one is allocated and initialized. + * + * \note Use with care, not on Armature poses but for temporal ones. + * \note (currently used for action constraints and in rebuild_pose). + */ struct bPoseChannel *BKE_pose_channel_ensure(struct bPose *pose, const char *name); +/** + * \see #ED_armature_ebone_get_mirrored (edit-mode, matching function) + */ struct bPoseChannel *BKE_pose_channel_get_mirrored(const struct bPose *pose, const char *name); void BKE_pose_check_uuids_unique_and_report(const struct bPose *pose); @@ -170,37 +304,60 @@ void BKE_pose_check_uuids_unique_and_report(const struct bPose *pose); bool BKE_pose_channels_is_valid(const struct bPose *pose); #endif -/* sets constraint flags */ +/** + * Checks for IK constraint, Spline IK, and also for Follow-Path constraint. + * can do more constraints flags later. pose should be entirely OK. + */ void BKE_pose_update_constraint_flags(struct bPose *pose); -/* tag constraint flags for update */ +/** + * Tag constraint flags for update. + */ void BKE_pose_tag_update_constraint_flags(struct bPose *pose); -/* return the name of structure pointed by pose->ikparam */ +/** + * Return the name of structure pointed by `pose->ikparam`. + */ const char *BKE_pose_ikparam_get_name(struct bPose *pose); -/* allocate and initialize pose->ikparam according to pose->iksolver */ +/** + * Allocate and initialize `pose->ikparam` according to `pose->iksolver`. + */ void BKE_pose_ikparam_init(struct bPose *pose); -/* initialize a bItasc structure with default value */ +/** + * Initialize a #bItasc structure with default value. + */ void BKE_pose_itasc_init(struct bItasc *itasc); -/* Checks if a bone is part of an IK chain or not */ +/** + * Checks if a bone is part of an IK chain or not. + */ bool BKE_pose_channel_in_IK_chain(struct Object *ob, struct bPoseChannel *pchan); /* Bone Groups API --------------------- */ -/* Adds a new bone-group */ +/** + * Adds a new bone-group (name may be NULL). + */ struct bActionGroup *BKE_pose_add_group(struct bPose *pose, const char *name); -/* Remove a bone-group */ -void BKE_pose_remove_group(struct bPose *pose, struct bActionGroup *grp, const int index); -/* Remove the matching bone-group from its index */ -void BKE_pose_remove_group_index(struct bPose *pose, const int index); +/** + * Remove the given bone-group (expects 'virtual' index (+1 one, used by active_group etc.)) + * index might be invalid ( < 1), in which case it will be find from grp. + */ +void BKE_pose_remove_group(struct bPose *pose, struct bActionGroup *grp, int index); +/** + * Remove the indexed bone-group (expects 'virtual' index (+1 one, used by active_group etc.)). + */ +void BKE_pose_remove_group_index(struct bPose *pose, int index); /* Assorted Evaluation ----------------- */ -/* Used for the Action Constraint */ +/** + * For the calculation of the effects of an Action at the given frame on an object + * This is currently only used for the Action Constraint + */ void what_does_obaction(struct Object *ob, struct Object *workob, struct bPose *pose, @@ -211,11 +368,18 @@ void what_does_obaction(struct Object *ob, /* for proxy */ void BKE_pose_copy_pchan_result(struct bPoseChannel *pchanto, const struct bPoseChannel *pchanfrom); +/** + * Both poses should be in sync. + */ bool BKE_pose_copy_result(struct bPose *to, struct bPose *from); -/* Clear transforms. */ +/** + * Zero the pose transforms for the entire pose or only for selected bones. + */ void BKE_pose_rest(struct bPose *pose, bool selected_bones_only); -/* Tag pose for recalc. Also tag all related data to be recalc. */ +/** + * Tag pose for recalculation. Also tag all related data to be recalculated. + */ void BKE_pose_tag_recalc(struct Main *bmain, struct bPose *pose); void BKE_pose_blend_write(struct BlendWriter *writer, struct bPose *pose, struct bArmature *arm); diff --git a/source/blender/blenkernel/BKE_anim_data.h b/source/blender/blenkernel/BKE_anim_data.h index 14ab9f21424..81c49917b63 100644 --- a/source/blender/blenkernel/BKE_anim_data.h +++ b/source/blender/blenkernel/BKE_anim_data.h @@ -43,48 +43,81 @@ struct bAction; /* ************************************* */ /* AnimData API */ -/* Check if the given ID-block can have AnimData */ -bool id_type_can_have_animdata(const short id_type); +/** + * Check if the given ID-block can have AnimData. + */ +bool id_type_can_have_animdata(short id_type); bool id_can_have_animdata(const struct ID *id); -/* Get AnimData from the given ID-block */ +/** + * Get #AnimData from the given ID-block. + */ struct AnimData *BKE_animdata_from_id(struct ID *id); -/* Ensure AnimData is present in the ID-block (when supported). */ +/** + * Ensure #AnimData exists in the given ID-block (when supported). + */ struct AnimData *BKE_animdata_ensure_id(struct ID *id); -/* Set active action used by AnimData from the given ID-block */ +/** + * Set active action used by AnimData from the given ID-block. + * + * Called when user tries to change the active action of an #AnimData block + * (via RNA, Outliner, etc.) + * + * \param reports: Can be NULL. + * \param id: The owner of the animation data + * \param act: The Action to set, or NULL to clear. + * + * \return true when the action was successfully updated, false otherwise. + */ bool BKE_animdata_set_action(struct ReportList *reports, struct ID *id, struct bAction *act); bool BKE_animdata_action_editable(const struct AnimData *adt); -/* Ensure that the action's idroot is set correctly given the ID type of the owner. - * Return true if it is, false if it was already set to an incompatible type. */ +/** + * Ensure that the action's idroot is set correctly given the ID type of the owner. + * Return true if it is, false if it was already set to an incompatible type. + */ bool BKE_animdata_action_ensure_idroot(const struct ID *owner, struct bAction *action); -/* Free AnimData */ -void BKE_animdata_free(struct ID *id, const bool do_id_user); +/** + * Free AnimData used by the nominated ID-block, and clear ID-block's AnimData pointer. + */ +void BKE_animdata_free(struct ID *id, bool do_id_user); -/* Return true if the ID-block has non-empty AnimData. */ +/** + * Return true if the ID-block has non-empty AnimData. + */ bool BKE_animdata_id_is_animated(const struct ID *id); +/** + * Callback used by lib_query to walk over all ID usages + * (mimics `foreach_id` callback of #IDTypeInfo structure). + */ void BKE_animdata_foreach_id(struct AnimData *adt, struct LibraryForeachIDData *data); -/* Copy AnimData */ -struct AnimData *BKE_animdata_copy(struct Main *bmain, struct AnimData *adt, const int flag); +/** + * Make a copy of the given AnimData - to be used when copying data-blocks. + * \param flag: Control ID pointers management, + * see LIB_ID_CREATE_.../LIB_ID_COPY_... flags in BKE_lib_id.h + * \return The copied animdata. + */ +struct AnimData *BKE_animdata_copy(struct Main *bmain, struct AnimData *adt, int flag); -/* Copy AnimData */ -bool BKE_animdata_copy_id(struct Main *bmain, - struct ID *id_to, - struct ID *id_from, - const int flag); +/** + * \param flag: Control ID pointers management, + * see LIB_ID_CREATE_.../LIB_ID_COPY_... flags in BKE_lib_id.h + * \return true is successfully copied. + */ +bool BKE_animdata_copy_id(struct Main *bmain, struct ID *id_to, struct ID *id_from, int flag); -/* Copy AnimData Actions */ +/** + * Copy AnimData Actions. + */ void BKE_animdata_copy_id_action(struct Main *bmain, struct ID *id); -void BKE_animdata_duplicate_id_action(struct Main *bmain, - struct ID *id, - const uint duplicate_flags); +void BKE_animdata_duplicate_id_action(struct Main *bmain, struct ID *id, uint duplicate_flags); /* Merge copies of data from source AnimData block */ typedef enum eAnimData_MergeCopy_Modes { @@ -98,6 +131,9 @@ typedef enum eAnimData_MergeCopy_Modes { ADT_MERGECOPY_SRC_REF = 2, } eAnimData_MergeCopy_Modes; +/** + * Merge copies of the data from the src AnimData into the destination AnimData. + */ void BKE_animdata_merge_copy(struct Main *bmain, struct ID *dst_id, struct ID *src_id, diff --git a/source/blender/blenkernel/BKE_anim_path.h b/source/blender/blenkernel/BKE_anim_path.h index 9db63080fd9..6bc09eb35ed 100644 --- a/source/blender/blenkernel/BKE_anim_path.h +++ b/source/blender/blenkernel/BKE_anim_path.h @@ -35,12 +35,21 @@ struct Object; int BKE_anim_path_get_array_size(const struct CurveCache *curve_cache); float BKE_anim_path_get_length(const struct CurveCache *curve_cache); -/* This function populates the 'ob->runtime.curve_cache->anim_path_accum_length' data. +/** + * This function populates the 'ob->runtime.curve_cache->anim_path_accum_length' data. * You should never have to call this manually as it should already have been called by * 'BKE_displist_make_curveTypes'. Do not call this manually unless you know what you are doing. */ void BKE_anim_path_calc_data(struct Object *ob); +/** + * Calculate the deformation implied by the curve path at a given parametric position, + * and returns whether this operation succeeded. + * + * \param ctime: Time is normalized range <0-1>. + * + * \return success. + */ bool BKE_where_on_path(const struct Object *ob, float ctime, float r_vec[4], diff --git a/source/blender/blenkernel/BKE_anim_visualization.h b/source/blender/blenkernel/BKE_anim_visualization.h index 4e86abeed8d..3b8c91b7fd2 100644 --- a/source/blender/blenkernel/BKE_anim_visualization.h +++ b/source/blender/blenkernel/BKE_anim_visualization.h @@ -38,13 +38,34 @@ struct bPoseChannel; /* ---------------------------------------------------- */ /* Animation Visualization */ +/** + * Initialize the default settings for animation visualization. + */ void animviz_settings_init(struct bAnimVizSettings *avs); +/** + * Make a copy of motion-path data, so that viewing with copy on write works. + */ struct bMotionPath *animviz_copy_motionpath(const struct bMotionPath *mpath_src); +/** + * Free the given motion path's cache. + */ void animviz_free_motionpath_cache(struct bMotionPath *mpath); +/** + * Free the given motion path instance and its data. + * \note this frees the motion path given! + */ void animviz_free_motionpath(struct bMotionPath *mpath); +/** + * Setup motion paths for the given data. + * \note Only used when explicitly calculating paths on bones which may/may not be consider already + * + * \param scene: Current scene (for frame ranges, etc.) + * \param ob: Object to add paths for (must be provided) + * \param pchan: Posechannel to add paths for (optional; if not provided, object-paths are assumed) + */ struct bMotionPath *animviz_verify_motionpaths(struct ReportList *reports, struct Scene *scene, struct Object *ob, diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h index 07da9d75e59..4845807de39 100644 --- a/source/blender/blenkernel/BKE_animsys.h +++ b/source/blender/blenkernel/BKE_animsys.h @@ -69,12 +69,17 @@ AnimationEvalContext BKE_animsys_eval_context_construct_at( /* ************************************* */ /* KeyingSets API */ -/* Used to create a new 'custom' KeyingSet for the user, - * that will be automatically added to the stack */ +/** + * Used to create a new 'custom' KeyingSet for the user, + * that will be automatically added to the stack. + */ struct KeyingSet *BKE_keyingset_add( struct ListBase *list, const char idname[], const char name[], short flag, short keyingflag); -/* Add a path to a KeyingSet */ +/** + * Add a path to a KeyingSet. Nothing is returned for now. + * Checks are performed to ensure that destination is appropriate for the KeyingSet in question + */ struct KS_Path *BKE_keyingset_add_path(struct KeyingSet *ks, struct ID *id, const char group_name[], @@ -83,7 +88,10 @@ struct KS_Path *BKE_keyingset_add_path(struct KeyingSet *ks, short flag, short groupmode); -/* Find the destination matching the criteria given */ +/** + * Find the destination matching the criteria given. + * TODO: do we want some method to perform partial matches too? + */ struct KS_Path *BKE_keyingset_find_path(struct KeyingSet *ks, struct ID *id, const char group_name[], @@ -113,7 +121,15 @@ void BKE_keyingsets_blend_read_expand(struct BlendExpander *expander, struct Lis /* ************************************* */ /* Path Fixing API */ -/* Get a "fixed" version of the given path (oldPath) */ +/** + * Get a "fixed" version of the given path `old_path`. + * + * This is just an external wrapper for the RNA-Path fixing function, + * with input validity checks on top of the basic method. + * + * \note it is assumed that the structure we're replacing is `<prefix><["><name><"]>` + * i.e. `pose.bones["Bone"]`. + */ char *BKE_animsys_fix_rna_path_rename(struct ID *owner_id, char *old_path, const char *prefix, @@ -123,7 +139,15 @@ char *BKE_animsys_fix_rna_path_rename(struct ID *owner_id, int newSubscript, bool verify_paths); -/* Fix all the paths for the given ID + Action */ +/** + * Fix all the paths for the given ID + Action. + * + * This is just an external wrapper for the F-Curve fixing function, + * with input validity checks on top of the basic method. + * + * \note it is assumed that the structure we're replacing is `<prefix><["><name><"]>` + * i.e. `pose.bones["Bone"]`. + */ void BKE_action_fix_paths_rename(struct ID *owner_id, struct bAction *act, const char *prefix, @@ -133,7 +157,12 @@ void BKE_action_fix_paths_rename(struct ID *owner_id, int newSubscript, bool verify_paths); -/* Fix all the paths for the given ID+AnimData */ +/** + * Fix all the paths for the given ID+AnimData + * + * \note it is assumed that the structure we're replacing is `<prefix><["><name><"]>` + * i.e. `pose.bones["Bone"]`. + */ void BKE_animdata_fix_paths_rename(struct ID *owner_id, struct AnimData *adt, struct ID *ref_id, @@ -144,24 +173,31 @@ void BKE_animdata_fix_paths_rename(struct ID *owner_id, int newSubscript, bool verify_paths); -/* Fix all the paths for the entire database... */ -void BKE_animdata_fix_paths_rename_all(struct ID *ref_id, - const char *prefix, - const char *oldName, - const char *newName); - -/* Fix all the paths for the entire bmain with extra parameters. */ +/** + * Fix all RNA-Paths throughout the database (directly access the #Global.main version). + * + * \note it is assumed that the structure we're replacing is `<prefix><["><name><"]>` + * i.e. `pose.bones["Bone"]` + */ void BKE_animdata_fix_paths_rename_all_ex(struct Main *bmain, struct ID *ref_id, const char *prefix, const char *oldName, const char *newName, - const int oldSubscript, - const int newSubscript, - const bool verify_paths); + int oldSubscript, + int newSubscript, + bool verify_paths); + +/** See #BKE_animdata_fix_paths_rename_all_ex */ +void BKE_animdata_fix_paths_rename_all(struct ID *ref_id, + const char *prefix, + const char *oldName, + const char *newName); -/* Fix the path after removing elements that are not ID (e.g., node). - * Return true if any animation data was affected. */ +/** + * Fix the path after removing elements that are not ID (e.g., node). + * Return true if any animation data was affected. + */ bool BKE_animdata_fix_paths_remove(struct ID *id, const char *path); /* -------------------------------------- */ @@ -172,17 +208,19 @@ typedef struct AnimationBasePathChange { const char *dst_basepath; } AnimationBasePathChange; -/* Move animation data from src to destination if its paths are based on basepaths */ +/** + * Move animation data from source to destination if its paths are based on `basepaths`. + * + * Transfer the animation data from `srcID` to `dstID` where the `srcID` animation data + * is based off `basepath`, creating new #AnimData and associated data as necessary. + * + * \param basepaths: A list of #AnimationBasePathChange. + */ void BKE_animdata_transfer_by_basepath(struct Main *bmain, struct ID *srcID, struct ID *dstID, struct ListBase *basepaths); -char *BKE_animdata_driver_path_hack(struct bContext *C, - struct PointerRNA *ptr, - struct PropertyRNA *prop, - char *base_path); - /* ************************************* */ /* Batch AnimData API */ @@ -195,7 +233,7 @@ typedef void (*ID_FCurve_Edit_Callback)(struct ID *id, struct FCurve *fcu, void /* Loop over all datablocks applying callback */ void BKE_animdata_main_cb(struct Main *bmain, ID_AnimData_Edit_Callback func, void *user_data); -/* Loop over all datablocks applying callback to all its F-Curves */ +/** Apply the given callback function on all F-Curves attached to data in `main` database. */ void BKE_fcurves_main_cb(struct Main *bmain, ID_FCurve_Edit_Callback func, void *user_data); /* Look over all f-curves of a given ID. */ @@ -208,11 +246,32 @@ void BKE_fcurves_id_cb(struct ID *id, ID_FCurve_Edit_Callback func, void *user_d typedef struct NlaKeyframingContext NlaKeyframingContext; +/** + * Prepare data necessary to compute correct keyframe values for NLA strips + * with non-Replace mode or influence different from 1. + * + * \param cache: List used to cache contexts for reuse when keying + * multiple channels in one operation. + * \param ptr: RNA pointer to the Object with the animation. + * \return Keyframing context, or NULL if not necessary. + */ struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( struct ListBase *cache, struct PointerRNA *ptr, struct AnimData *adt, const struct AnimationEvalContext *anim_eval_context); +/** + * Apply correction from the NLA context to the values about to be keyframed. + * + * \param context: Context to use (may be NULL). + * \param prop_ptr: Property about to be keyframed. + * \param[in,out] values: Array of property values to adjust. + * \param count: Number of values in the array. + * \param index: Index of the element about to be updated, or -1. + * \param[out] r_force_all: Set to true if all channels must be inserted. May be NULL. + * \return False if correction fails due to a division by zero, + * or null r_force_all when all channels are required. + */ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, struct PointerRNA *prop_ptr, struct PropertyRNA *prop, @@ -220,6 +279,9 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, int count, int index, bool *r_force_all); +/** + * Free all cached contexts from the list. + */ void BKE_animsys_free_nla_keyframing_context_cache(struct ListBase *cache); /* ************************************* */ @@ -237,19 +299,35 @@ typedef enum eAnimData_Recalc { bool BKE_animsys_rna_path_resolve(struct PointerRNA *ptr, const char *rna_path, - const int array_index, + int array_index, struct PathResolvedRNA *r_result); bool BKE_animsys_read_from_rna_path(struct PathResolvedRNA *anim_rna, float *r_value); -bool BKE_animsys_write_to_rna_path(struct PathResolvedRNA *anim_rna, const float value); +/** + * Write the given value to a setting using RNA, and return success. + */ +bool BKE_animsys_write_to_rna_path(struct PathResolvedRNA *anim_rna, float value); -/* Evaluation loop for evaluating animation data. */ +/** + * Evaluation loop for evaluation animation data + * + * This assumes that the animation-data provided belongs to the ID block in question, + * and that the flags for which parts of the animation-data settings need to be recalculated + * have been set already by the depsgraph. Now, we use the recalculate. + */ void BKE_animsys_evaluate_animdata(struct ID *id, struct AnimData *adt, const struct AnimationEvalContext *anim_eval_context, eAnimData_Recalc recalc, - const bool flush_to_original); + bool flush_to_original); -/* Evaluation of all ID-blocks with Animation Data blocks - Animation Data Only */ +/** + * Evaluation of all ID-blocks with Animation Data blocks - Animation Data Only + * + * This will evaluate only the animation info available in the animation data-blocks + * encountered. In order to enforce the system by which some settings controlled by a + * 'local' (i.e. belonging in the nearest ID-block that setting is related to, not a + * standard 'root') block are overridden by a larger 'user' + */ void BKE_animsys_evaluate_all_animation(struct Main *main, struct Depsgraph *depsgraph, float ctime); diff --git a/source/blender/blenkernel/BKE_appdir.h b/source/blender/blenkernel/BKE_appdir.h index fee52479cd0..a7baaed141f 100644 --- a/source/blender/blenkernel/BKE_appdir.h +++ b/source/blender/blenkernel/BKE_appdir.h @@ -16,62 +16,150 @@ #pragma once /** \file - * \ingroup bli + * \ingroup bke + * + * \note on naming: typical _get() suffix is omitted here, + * since its the main purpose of the API. */ #include <stddef.h> +#include "BLI_compiler_attrs.h" + #ifdef __cplusplus extern "C" { #endif struct ListBase; +/** + * Sanity check to ensure correct API use in debug mode. + * + * Run this once the first level of arguments has been passed so we can be sure + * `--env-system-datafiles`, and other `--env-*` arguments has been passed. + * + * Without this any callers to this module that run early on, + * will miss out on changes from parsing arguments. + */ void BKE_appdir_init(void); 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); +/** + * 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. + */ +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; +/** + * Get the user's home directory, i.e. + * - Unix: `$HOME` + * - Windows: `%userprofile%` + */ const char *BKE_appdir_folder_home(void); +/** + * 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. + */ bool BKE_appdir_folder_documents(char *dir); -bool BKE_appdir_folder_id_ex(const int folder_id, - const char *subfolder, - char *path, - size_t path_len); -const char *BKE_appdir_folder_id(const int folder_id, const char *subfolder); -const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder); -const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder); -const char *BKE_appdir_folder_id_version(const int folder_id, - const int version, - const bool check_is_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, size_t path_len); +/** + * Get a folder out of the \a folder_id presets for paths. + * + * \param subfolder: The name of a directory to check for, + * this may contain path separators but must resolve to a directory, checked with #BLI_is_dir. + * \return The path if found, NULL string if not. + */ +bool BKE_appdir_folder_id_ex(int folder_id, const char *subfolder, char *path, size_t path_len); +const char *BKE_appdir_folder_id(int folder_id, const char *subfolder); +/** + * Returns the path to a folder in the user area, creating it if it doesn't exist. + */ +const char *BKE_appdir_folder_id_create(int folder_id, const char *subfolder); +/** + * Returns the path to a folder in the user area without checking that it actually exists first. + */ +const char *BKE_appdir_folder_id_user_notest(int folder_id, const char *subfolder); +/** + * Returns the path of the top-level version-specific local, user or system directory. + * If check_is_dir, then the result will be NULL if the directory doesn't exist. + */ +const char *BKE_appdir_folder_id_version(int folder_id, int version, bool check_is_dir); +/** + * Check if this is an install with user files kept together + * with the Blender executable and its installation files. + */ bool BKE_appdir_app_is_portable_install(void); +/** + * Return true if templates exist + */ bool BKE_appdir_app_template_any(void); bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len); bool BKE_appdir_app_template_has_userpref(const char *app_template); void BKE_appdir_app_templates(struct ListBase *templates); -/* Initialize path to program executable */ +/** + * Initialize path to program executable. + */ void BKE_appdir_program_path_init(const char *argv0); +/** + * Path to executable + */ const char *BKE_appdir_program_path(void); +/** + * Path to directory of executable + */ const char *BKE_appdir_program_dir(void); -/* Return OS fonts directory. */ +/** + * Gets a good default directory for fonts. + */ bool BKE_appdir_font_folder_default(char *dir); -/* find python executable */ +/** + * Find Python executable. + */ bool BKE_appdir_program_python_search(char *fullpath, - const size_t fullpath_len, - const int version_major, - const int version_minor); + size_t fullpath_len, + int version_major, + int version_minor); -/* Initialize path to temporary directory. */ +/** + * Initialize path to temporary directory. + */ void BKE_tempdir_init(const char *userdir); +/** + * Path to persistent temporary directory (with trailing slash) + */ const char *BKE_tempdir_base(void); +/** + * Path to temporary directory (with trailing slash) + */ const char *BKE_tempdir_session(void); +/** + * Delete content of this instance's temp dir. + */ void BKE_tempdir_session_purge(void); /* folder_id */ diff --git a/source/blender/blenkernel/BKE_armature.h b/source/blender/blenkernel/BKE_armature.h index e13475fd78c..8584ce6f508 100644 --- a/source/blender/blenkernel/BKE_armature.h +++ b/source/blender/blenkernel/BKE_armature.h @@ -152,13 +152,13 @@ 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(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); +void BKE_armature_bonelist_free(struct ListBase *lb, bool do_id_user); +void BKE_armature_editbonelist_free(struct ListBase *lb, bool do_id_user); void BKE_armature_copy_bone_transforms(struct bArmature *armature_dst, const struct bArmature *armature_src); -void BKE_armature_transform(struct bArmature *arm, const float mat[4][4], const bool do_props); +void BKE_armature_transform(struct bArmature *arm, const float mat[4][4], bool do_props); /* Bounding box. */ struct BoundBox *BKE_armature_boundbox_get(struct Object *ob); @@ -166,8 +166,20 @@ struct BoundBox *BKE_armature_boundbox_get(struct Object *ob); bool BKE_pose_minmax( struct Object *ob, float r_min[3], float r_max[3], bool use_hidden, bool use_select); +/** + * Finds the best possible extension to the name on a particular axis. + * (For renaming, check for unique names afterwards) + * \param strip_number: removes number extensions (TODO: not used). + * \param axis: The axis to name on. + * \param head: The head co-ordinate of the bone on the specified axis. + * \param tail: The tail co-ordinate of the bone on the specified axis. + */ bool bone_autoside_name(char name[64], int strip_number, short axis, float head, float tail); +/** + * Walk the list until the bone is found (slow!), + * use #BKE_armature_bone_from_name_map for multiple lookups. + */ struct Bone *BKE_armature_find_bone_name(struct bArmature *arm, const char *name); void BKE_armature_bone_hash_make(struct bArmature *arm); @@ -177,40 +189,87 @@ bool BKE_armature_bone_flag_test_recursive(const struct Bone *bone, int flag); void BKE_armature_refresh_layer_used(struct Depsgraph *depsgraph, struct bArmature *arm); +/** + * Using `vec` with dist to bone `b1 - b2`. + */ float distfactor_to_bone( const float vec[3], const float b1[3], const float b2[3], float rad1, float rad2, float rdist); +/** + * Updates vectors and matrices on rest-position level, only needed + * after editing armature itself, now only on reading file. + */ void BKE_armature_where_is(struct bArmature *arm); +/** + * Recursive part, calculates rest-position of entire tree of children. + * \note Used when exiting edit-mode too. + */ void BKE_armature_where_is_bone(struct Bone *bone, const struct Bone *bone_parent, - const bool use_recursion); + bool use_recursion); +/** + * Clear pointers of object's pose + * (needed in remap case, since we cannot always wait for a complete pose rebuild). + */ void BKE_pose_clear_pointers(struct bPose *pose); void BKE_pose_remap_bone_pointers(struct bArmature *armature, struct bPose *pose); +/** + * Update the links for the B-Bone handles from Bone data. + */ void BKE_pchan_rebuild_bbone_handles(struct bPose *pose, struct bPoseChannel *pchan); -void BKE_pose_channels_clear_with_null_bone(struct bPose *pose, const bool do_id_user); +void BKE_pose_channels_clear_with_null_bone(struct bPose *pose, bool do_id_user); +/** + * Only after leave edit-mode, duplicating, validating older files, library syncing. + * + * \note pose->flag is set for it. + * + * \param bmain: May be NULL, only used to tag depsgraph as being dirty. + */ void BKE_pose_rebuild(struct Main *bmain, struct Object *ob, struct bArmature *arm, - const bool do_id_user); + bool do_id_user); +/** + * Ensures object's pose is rebuilt if needed. + * + * \param bmain: May be NULL, only used to tag depsgraph as being dirty. + */ void BKE_pose_ensure(struct Main *bmain, struct Object *ob, struct bArmature *arm, - const bool do_id_user); + bool do_id_user); +/** + * \note This is the only function adding poses. + * \note This only reads anim data from channels, and writes to channels. + */ void BKE_pose_where_is(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * The main armature solver, does all constraints excluding IK. + * + * \param pchan: pose-channel - validated, as having bone and parent pointer. + * \param do_extra: when zero skips loc/size/rot, constraints and strip modifiers. + */ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct bPoseChannel *pchan, float ctime, bool do_extra); +/** + * Calculate tail of pose-channel. + */ 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. */ +/** + * 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_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. */ +/** + * 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); @@ -220,25 +279,64 @@ void BKE_pose_apply_action_blend(struct Object *ob, 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]); +void vec_roll_to_mat3(const float vec[3], float roll, float r_mat[3][3]); + +/** + * Calculates the rest matrix of a bone based on its vector and a roll around that vector. + */ +void vec_roll_to_mat3_normalized(const float nor[3], float roll, float r_mat[3][3]); +/** + * Computes vector and roll based on a rotation. + * "mat" must contain only a rotation, and no scaling. + */ void mat3_to_vec_roll(const float mat[3][3], float r_vec[3], float *r_roll); +/** + * Computes roll around the vector that best approximates the matrix. + * If `vec` is the Y vector from purely rotational `mat`, result should be exact. + */ void mat3_vec_to_roll(const float mat[3][3], const float vec[3], float *r_roll); /* Common Conversions Between Co-ordinate Spaces */ + +/** + * Convert World-Space Matrix to Pose-Space Matrix. + */ void BKE_armature_mat_world_to_pose(struct Object *ob, const float inmat[4][4], float outmat[4][4]); +/** + * Convert World-Space Location to Pose-Space Location + * \note this cannot be used to convert to pose-space location of the supplied + * pose-channel into its local space (i.e. 'visual'-keyframing). + */ void BKE_armature_loc_world_to_pose(struct Object *ob, const float inloc[3], float outloc[3]); +/** + * Convert Pose-Space Matrix to Bone-Space Matrix. + * \note this cannot be used to convert to pose-space transforms of the supplied + * pose-channel into its local space (i.e. 'visual'-keyframing). + */ void BKE_armature_mat_pose_to_bone(struct bPoseChannel *pchan, const float inmat[4][4], float outmat[4][4]); +/** + * Convert Pose-Space Location to Bone-Space Location + * \note this cannot be used to convert to pose-space location of the supplied + * pose-channel into its local space (i.e. 'visual'-keyframing). + */ void BKE_armature_loc_pose_to_bone(struct bPoseChannel *pchan, const float inloc[3], float outloc[3]); +/** + * Convert Bone-Space Matrix to Pose-Space Matrix. + */ void BKE_armature_mat_bone_to_pose(struct bPoseChannel *pchan, const float inmat[4][4], float outmat[4][4]); +/** + * Remove rest-position effects from pose-transform for obtaining + * 'visual' transformation of pose-channel. + * (used by the Visual-Keyframing stuff). + */ void BKE_armature_mat_pose_to_delta(float delta_mat[4][4], float pose_mat[4][4], float arm_mat[4][4]); @@ -249,13 +347,34 @@ void BKE_armature_mat_pose_to_bone_ex(struct Depsgraph *depsgraph, const float inmat[4][4], float outmat[4][4]); +/** + * Same as #BKE_object_mat3_to_rot(). + */ void BKE_pchan_mat3_to_rot(struct bPoseChannel *pchan, const float mat[3][3], bool use_compat); +/** + * Same as #BKE_object_rot_to_mat3(). + */ void BKE_pchan_rot_to_mat3(const struct bPoseChannel *pchan, float r_mat[3][3]); +/** + * Apply a 4x4 matrix to the pose bone, + * similar to #BKE_object_apply_mat4(). + */ void BKE_pchan_apply_mat4(struct bPoseChannel *pchan, const float mat[4][4], bool use_compat); +/** + * Convert the loc/rot/size to \a r_chanmat (typically #bPoseChannel.chan_mat). + */ void BKE_pchan_to_mat4(const struct bPoseChannel *pchan, float r_chanmat[4][4]); + +/** + * Convert the loc/rot/size to mat4 (`pchan.chan_mat`), + * used in `constraint.c` too. + */ void BKE_pchan_calc_mat(struct bPoseChannel *pchan); -/* Simple helper, computes the offset bone matrix. */ +/** + * Simple helper, computes the offset bone matrix: + * `offs_bone = yoffs(b-1) + root(b) + bonemat(b)`. + */ void BKE_bone_offset_matrix_get(const struct Bone *bone, float offs_bone[4][4]); /* Transformation inherited from the parent bone. These matrices apply the effects of @@ -277,9 +396,38 @@ void BKE_bone_parent_transform_apply(const struct BoneParentTransform *bpt, const float inmat[4][4], float outmat[4][4]); -/* Get the current parent transformation for the given pose bone. */ +/** + * Get the current parent transformation for the given pose bone. + * + * Construct the matrices (rot/scale and loc) + * to apply the PoseChannels into the armature (object) space. + * I.e. (roughly) the `pose_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b)` in the + * `pose_mat(b)= pose_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b) * chan_mat(b)` + * ...function. + * + * This allows to get the transformations of a bone in its object space, + * *before* constraints (and IK) get applied (used by pose evaluation code). + * And reverse: to find pchan transformations needed to place a bone at a given loc/rot/scale + * in object space (used by interactive transform, and snapping code). + * + * Note that, with the HINGE/NO_SCALE/NO_LOCAL_LOCATION options, the location matrix + * will differ from the rotation/scale matrix... + * + * \note This cannot be used to convert to pose-space transforms of the supplied + * pose-channel into its local space (i.e. 'visual'-keyframing). + * (NOTE(@mont29): I don't understand that, so I keep it :p). + */ void BKE_bone_parent_transform_calc_from_pchan(const struct bPoseChannel *pchan, struct BoneParentTransform *r_bpt); +/** + * Compute the parent transform using data decoupled from specific data structures. + * + * \param bone_flag: #Bone.flag containing settings. + * \param offs_bone: delta from parent to current arm_mat (or just arm_mat if no parent). + * \param parent_arm_mat: arm_mat of parent, or NULL. + * \param parent_pose_mat: pose_mat of parent, or NULL. + * \param r_bpt: OUTPUT parent transform. + */ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag, int inherit_scale_mode, const float offs_bone[4][4], @@ -287,7 +435,13 @@ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag, const float parent_pose_mat[4][4], struct BoneParentTransform *r_bpt); -/* Rotation Mode Conversions - Used for PoseChannels + Objects... */ +/** + * Rotation Mode Conversions - Used for Pose-Channels + Objects. + * + * Called from RNA when rotation mode changes + * - the result should be that the rotations given in the provided pointers have had conversions + * applied (as appropriate), such that the rotation of the element hasn't 'visually' changed. + */ void BKE_rotMode_change_values( float quat[4], float eul[3], float axis[3], float *angle, short oldMode, short newMode); @@ -320,18 +474,31 @@ typedef struct BBoneSplineParameters { float curve_in_x, curve_in_z, curve_out_x, curve_out_z; } BBoneSplineParameters; +/** + * Get "next" and "prev" bones - these are used for handle calculations. + */ void BKE_pchan_bbone_handles_get(struct bPoseChannel *pchan, struct bPoseChannel **r_prev, struct bPoseChannel **r_next); +/** + * Compute B-Bone spline parameters for the given channel. + */ void BKE_pchan_bbone_spline_params_get(struct bPoseChannel *pchan, - const bool rest, + bool rest, struct BBoneSplineParameters *r_param); +/** + * Fills the array with the desired amount of bone->segments elements. + * This calculation is done within unit bone space. + */ void BKE_pchan_bbone_spline_setup(struct bPoseChannel *pchan, - const bool rest, - const bool for_deform, + bool rest, + bool for_deform, Mat4 *result_array); +/** + * Computes the bezier handle vectors and rolls coming from custom handles. + */ void BKE_pchan_bbone_handles_compute(const BBoneSplineParameters *param, float h1[3], float *r_roll1, @@ -339,14 +506,28 @@ void BKE_pchan_bbone_handles_compute(const BBoneSplineParameters *param, float *r_roll2, bool ease, bool offsets); +/** + * Fills the array with the desired amount of `bone->segments` elements. + * This calculation is done within unit bone space. + */ int BKE_pchan_bbone_spline_compute(struct BBoneSplineParameters *param, - const bool for_deform, + bool for_deform, Mat4 *result_array); +/** + * Compute and cache the B-Bone shape in the channel runtime struct. + */ void BKE_pchan_bbone_segments_cache_compute(struct bPoseChannel *pchan); +/** + * Copy cached B-Bone segments from one channel to another. + */ void BKE_pchan_bbone_segments_cache_copy(struct bPoseChannel *pchan, struct bPoseChannel *pchan_from); +/** + * Calculate index and blend factor for the two B-Bone segment nodes + * affecting the point at 0 <= pos <= 1. + */ void BKE_pchan_bbone_deform_segment_index(const struct bPoseChannel *pchan, float pos, int *r_index, diff --git a/source/blender/blenkernel/BKE_asset.h b/source/blender/blenkernel/BKE_asset.h index 42eea41b7a7..58c50fb736f 100644 --- a/source/blender/blenkernel/BKE_asset.h +++ b/source/blender/blenkernel/BKE_asset.h @@ -20,6 +20,7 @@ #pragma once +#include "BLI_compiler_attrs.h" #include "BLI_utildefines.h" #include "DNA_asset_types.h" @@ -29,11 +30,23 @@ 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); @@ -44,6 +57,9 @@ struct AssetTagEnsureResult { }; struct AssetTag *BKE_asset_metadata_tag_add(struct AssetMetaData *asset_data, const char *name); +/** + * Make sure there is a tag with name \a name, create one if needed. + */ struct AssetTagEnsureResult BKE_asset_metadata_tag_ensure(struct AssetMetaData *asset_data, const char *name); void BKE_asset_metadata_tag_remove(struct AssetMetaData *asset_data, struct AssetTag *tag); @@ -56,6 +72,10 @@ void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, 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 index 05db3c808cf..004b70c2925 100644 --- a/source/blender/blenkernel/BKE_asset_catalog.hh +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -26,10 +26,13 @@ #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> @@ -37,35 +40,46 @@ namespace blender::bke { +class AssetCatalog; +class AssetCatalogCollection; +class AssetCatalogDefinitionFile; +class AssetCatalogFilter; +class AssetCatalogTree; + using CatalogID = bUUID; -using CatalogPath = std::string; 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; - -class AssetCatalog; -class AssetCatalogDefinitionFile; -class AssetCatalogTree; +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 char PATH_SEPARATOR; static const CatalogFilePath DEFAULT_CATALOG_FILENAME; public: - AssetCatalogService() = default; + 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 in response to the blend file being saved. + * 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: @@ -81,7 +95,16 @@ class AssetCatalogService { * * Return true on success, which either means there were no in-memory categories to save, * or the save was successful. */ - bool write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path); + bool write_to_disk(const CatalogFilePath &blend_file_path); + + /** + * Ensure that the next call to #on_blend_save_post() will choose a new location for the CDF + * suitable for the location of the blend file (regardless of where the current catalogs come + * from), and that catalogs will be merged with already-existing ones in that location. + * + * Use this for a "Save as..." that has to write the catalogs to the new blend file location, + * instead of updating the previously read CDF. */ + void prepare_to_merge_on_write(); /** * Merge on-disk changes into the in-memory asset catalogs. @@ -91,45 +114,113 @@ class AssetCatalogService { * - 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 merge_from_disk_before_writing(); + void reload_catalogs(); /** Return catalog with the given ID. Return nullptr if not found. */ - AssetCatalog *find_catalog(CatalogID catalog_id); + 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. */ - AssetCatalog *find_catalog_by_path(const CatalogPath &path) 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 CatalogPath &catalog_path); + * 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); /** - * Soft-delete the catalog, ensuring it actually gets deleted when the catalog definition file is - * written. */ - void delete_catalog(CatalogID catalog_id); + * 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 CatalogPath &new_catalog_path); + 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: - /* These pointers are owned by this AssetCatalogService. */ - Map<CatalogID, std::unique_ptr<AssetCatalog>> catalogs_; - Map<CatalogID, std::unique_ptr<AssetCatalog>> deleted_catalogs_; - std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_; - std::unique_ptr<AssetCatalogTree> catalog_tree_; + 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); @@ -150,6 +241,55 @@ class AssetCatalogService { 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 every catalog, mark it as "dirty". + */ + void tag_all_catalogs_as_unsaved_changes(); + + /* 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); }; /** @@ -166,13 +306,16 @@ class AssetCatalogTreeItem { AssetCatalogTreeItem(StringRef name, CatalogID catalog_id, + StringRef simple_name, const AssetCatalogTreeItem *parent = nullptr); CatalogID get_catalog_id() const; - StringRef get_name() 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. */ - CatalogPath catalog_path() const; + AssetCatalogPath catalog_path() const; int count_parents() const; bool has_children() const; @@ -186,6 +329,10 @@ class AssetCatalogTreeItem { /** 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). */ @@ -230,6 +377,9 @@ class AssetCatalogDefinitionFile { /* 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; + /* 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 static std::string VERSION_MARKER; const static std::string HEADER; @@ -252,13 +402,21 @@ class AssetCatalogDefinitionFile { bool write_to_disk(const CatalogFilePath &dest_file_path) const; bool contains(CatalogID catalog_id) const; - /* Add a new catalog. Undefined behavior if a catalog with the same ID was already added. */ + /** 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*`. */ @@ -280,31 +438,38 @@ class AssetCatalogDefinitionFile { class AssetCatalog { public: AssetCatalog() = default; - AssetCatalog(CatalogID catalog_id, const CatalogPath &path, const std::string &simple_name); + AssetCatalog(CatalogID catalog_id, const AssetCatalogPath &path, const std::string &simple_name); CatalogID catalog_id; - CatalogPath path; + 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. */ + * we also store a human-readable simple name for the catalog. + * + * It should fit in sizeof(AssetMetaData::catalog_simple_name) bytes. */ 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; - } flags; - /** - * \return true only if this catalog's path is contained within the given path. - * When this catalog's path is equal to the given path, return true as well. - * - * Note that non-normalized paths (so for example starting or ending with a slash) are not - * supported, and result in undefined behavior. - */ - bool is_contained_in(const CatalogPath &other_path) const; + /* 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. @@ -312,28 +477,57 @@ class AssetCatalog { * 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 CatalogPath &path); - static CatalogPath cleanup_path(const CatalogPath &path); + 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 CatalogPath &path); + static std::string sensible_simple_name_for_path(const AssetCatalogPath &path); }; -/** Comparator for asset catalogs, ordering by (path, UUID). */ -struct AssetCatalogPathCmp { +/** 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->catalog_id < rhs->catalog_id; + 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->path < rhs->path; + + 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 *, AssetCatalogPathCmp>; +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 index 04486a7c132..ca12fd6f4fb 100644 --- a/source/blender/blenkernel/BKE_asset_library.h +++ b/source/blender/blenkernel/BKE_asset_library.h @@ -29,9 +29,14 @@ extern "C" { /** Forward declaration, defined in intern/asset_library.hh */ typedef struct AssetLibrary AssetLibrary; -/** TODO(@sybren): properly have a think/discussion about the API for this. */ +/** + * 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); -void BKE_asset_library_free(struct AssetLibrary *asset_library); /** * Try to find an appropriate location for an asset library root from a file or directory path. @@ -68,6 +73,13 @@ bool BKE_asset_library_find_suitable_root_path_from_path( 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 index fc5e137dd3e..37e2ad38a29 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -33,18 +33,44 @@ 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); - void on_save_handler_register(); - void on_save_handler_unregister(); + /** Load catalogs that have changed on disk. */ + void refresh(); - void on_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); + /** + * 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, int num_pointers); private: - bCallbackFuncStore on_save_callback_store_; + 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 7476474258b..6020da08f51 100644 --- a/source/blender/blenkernel/BKE_attribute.h +++ b/source/blender/blenkernel/BKE_attribute.h @@ -38,17 +38,14 @@ struct ID; struct ReportList; /* Attribute.domain */ -/** - * \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. */ - ATTR_DOMAIN_POINT = 0, /* Mesh, Hair or PointCloud Point */ - ATTR_DOMAIN_EDGE = 1, /* Mesh Edge */ - ATTR_DOMAIN_FACE = 2, /* Mesh Face */ - ATTR_DOMAIN_CORNER = 3, /* Mesh Corner */ - ATTR_DOMAIN_CURVE = 4, /* Hair Curve */ + ATTR_DOMAIN_AUTO = -1, /* Use for nodes to choose automatically based on other data. */ + ATTR_DOMAIN_POINT = 0, /* Mesh, Hair or PointCloud Point */ + ATTR_DOMAIN_EDGE = 1, /* Mesh Edge */ + ATTR_DOMAIN_FACE = 2, /* Mesh Face */ + ATTR_DOMAIN_CORNER = 3, /* Mesh Corner */ + ATTR_DOMAIN_CURVE = 4, /* Hair Curve */ + ATTR_DOMAIN_INSTANCE = 5, /* Instance */ ATTR_DOMAIN_NUM } AttributeDomain; @@ -57,19 +54,16 @@ typedef enum AttributeDomain { bool BKE_id_attributes_supported(struct ID *id); -struct CustomDataLayer *BKE_id_attribute_new(struct ID *id, - const char *name, - const int type, - const AttributeDomain domain, - struct ReportList *reports); +struct CustomDataLayer *BKE_id_attribute_new( + struct ID *id, const char *name, int type, AttributeDomain domain, struct ReportList *reports); 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); + int type, + 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); @@ -79,7 +73,7 @@ bool BKE_id_attribute_rename(struct ID *id, const char *new_name, struct ReportList *reports); -int BKE_id_attributes_length(struct ID *id, const CustomDataMask mask); +int BKE_id_attributes_length(struct ID *id, CustomDataMask mask); struct CustomDataLayer *BKE_id_attributes_active_get(struct ID *id); void BKE_id_attributes_active_set(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 da3de2f08bd..3ffdcee05eb 100644 --- a/source/blender/blenkernel/BKE_attribute_access.hh +++ b/source/blender/blenkernel/BKE_attribute_access.hh @@ -26,9 +26,41 @@ #include "BKE_attribute.h" #include "BLI_color.hh" -#include "BLI_float2.hh" -#include "BLI_float3.hh" #include "BLI_function_ref.hh" +#include "BLI_math_vec_types.hh" + +/** + * This file defines classes that help to provide access to attribute data on a #GeometryComponent. + * The API for retrieving attributes is defined in `BKE_geometry_set.hh`, but this comment has some + * general comments about the system. + * + * Attributes are stored in geometry data, though they can also be stored in instances. Their + * storage is often tied to `CustomData`, which is a system to store "layers" of data with specific + * types and names. However, since `CustomData` was added to Blender before attributes were + * conceptualized, it combines the "legacy" style of task-specific attribute types with generic + * types like "Float". The attribute API here only provides access to generic types. + * + * Attributes are retrieved from geometry components by providing an "id" (#AttributeIDRef). This + * is most commonly just an attribute name. The attribute API on geometry components has some more + * advanced capabilities: + * 1. Read-only access: With a `const` geometry component, an attribute on the geometry cannot be + * modified, so the `for_write` and `for_output` versions of the API are not available. This is + * extremely important for writing coherent bug-free code. When an attribute is retrieved with + * write access, via #WriteAttributeLookup or #OutputAttribute, the geometry component must be + * tagged to clear caches that depend on the changed data. + * 2. Domain interpolation: When retrieving an attribute, a domain (#AttributeDomain) can be + * provided. If the attribute is stored on a different domain and conversion is possible, a + * version of the data interpolated to the requested domain will be provided. These conversions + * are implemented in each #GeometryComponent by `attribute_try_adapt_domain_impl`. + * 3. Implicit type conversion: In addition to interpolating domains, attribute types can be + * converted, using the conversions in `BKE_type_conversions.hh`. The #VArray / #GVArray system + * makes it possible to only convert necessary indices on-demand. + * 4. Anonymous attributes: The "id" used to look up an attribute can also be an anonymous + * attribute reference. Currently anonymous attributes are only used in geometry nodes. + * 5. Abstracted storage: Since the data returned from the API is usually a virtual array, + * it doesn't have to be stored contiguously (even though that is generally preferred). This + * allows accessing "legacy" attributes like `material_index`, which is stored in `MPoly`. + */ namespace blender::bke { @@ -42,66 +74,22 @@ class AttributeIDRef { const AnonymousAttributeID *anonymous_id_ = nullptr; public: - AttributeIDRef() = default; - - AttributeIDRef(StringRef name) : name_(name) - { - } - - AttributeIDRef(StringRefNull name) : name_(name) - { - } - - AttributeIDRef(const char *name) : name_(name) - { - } - - AttributeIDRef(const std::string &name) : name_(name) - { - } - - /* The anonymous id is only borrowed, the caller has to keep a reference to it. */ - AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id) - { - } - - operator bool() const - { - return this->is_named() || this->is_anonymous(); - } - - friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) - { - return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; - } - - uint64_t hash() const - { - return get_default_hash_2(name_, anonymous_id_); - } - - bool is_named() const - { - return !name_.is_empty(); - } - - bool is_anonymous() const - { - return anonymous_id_ != nullptr; - } - - StringRef name() const - { - BLI_assert(this->is_named()); - return name_; - } - - const AnonymousAttributeID &anonymous_id() const - { - BLI_assert(this->is_anonymous()); - return *anonymous_id_; - } - + 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); }; @@ -159,10 +147,10 @@ struct AttributeInitDefault : public AttributeInit { * Note that this can be used to fill the new attribute with the default */ struct AttributeInitVArray : public AttributeInit { - const blender::fn::GVArray *varray; + blender::fn::GVArray varray; - AttributeInitVArray(const blender::fn::GVArray *varray) - : AttributeInit(Type::VArray), varray(varray) + AttributeInitVArray(blender::fn::GVArray varray) + : AttributeInit(Type::VArray), varray(std::move(varray)) { } }; @@ -194,13 +182,15 @@ namespace blender::bke { using fn::CPPType; using fn::GVArray; -using fn::GVArrayPtr; using fn::GVMutableArray; -using fn::GVMutableArrayPtr; const CPPType *custom_data_type_to_cpp_type(const CustomDataType type); CustomDataType cpp_type_to_custom_data_type(const CPPType &type); CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_types); +/** + * Domains with a higher "information density" have a higher priority, + * in order to choose a domain that will not lose data through domain conversion. + */ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains); /** @@ -208,14 +198,14 @@ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) */ struct ReadAttributeLookup { /* The virtual array that is used to read from this attribute. */ - GVArrayPtr varray; + GVArray varray; /* Domain the attribute lives on in the geometry. */ AttributeDomain domain; /* Convenience function to check if the attribute has been found. */ operator bool() const { - return this->varray.get() != nullptr; + return this->varray; } }; @@ -223,15 +213,20 @@ struct ReadAttributeLookup { * Used when looking up a "plain attribute" based on a name for reading from it and writing to it. */ struct WriteAttributeLookup { - /* The virtual array that is used to read from and write to the attribute. */ - GVMutableArrayPtr varray; - /* Domain the attributes lives on in the geometry. */ + /** The virtual array that is used to read from and write to the attribute. */ + GVMutableArray varray; + /** Domain the attributes lives on in the geometry component. */ AttributeDomain domain; + /** + * Call this after changing the attribute to invalidate caches that depend on this attribute. + * \note Do not call this after the component the attribute is from has been destructed. + */ + std::function<void()> tag_modified_fn; /* Convenience function to check if the attribute has been found. */ operator bool() const { - return this->varray.get() != nullptr; + return this->varray; } }; @@ -245,87 +240,44 @@ struct WriteAttributeLookup { * VMutableArray_Span in many cases). * - An output attribute can live side by side with an existing attribute with a different domain * or data type. The old attribute will only be overwritten when the #save function is called. + * + * \note The lifetime of an output attribute should not be longer than the the lifetime of the + * geometry component it comes from, since it can keep a reference to the component for use in + * the #save method. */ class OutputAttribute { public: using SaveFn = std::function<void(OutputAttribute &)>; private: - GVMutableArrayPtr varray_; - AttributeDomain domain_; + GVMutableArray varray_; + AttributeDomain domain_ = ATTR_DOMAIN_AUTO; SaveFn save_; std::unique_ptr<fn::GVMutableArray_GSpan> optional_span_varray_; bool ignore_old_values_ = false; bool save_has_been_called_ = false; public: - OutputAttribute() = default; - - OutputAttribute(GVMutableArrayPtr varray, + OutputAttribute(); + OutputAttribute(OutputAttribute &&other); + OutputAttribute(GVMutableArray 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; + 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_; - } + operator bool() const; - const CPPType &cpp_type() const - { - return varray_->type(); - } + GVMutableArray &operator*(); + fn::GVMutableArray *operator->(); + GVMutableArray &varray(); + AttributeDomain domain() const; + const CPPType &cpp_type() const; + CustomDataType custom_data_type() const; - CustomDataType custom_data_type() const - { - return cpp_type_to_custom_data_type(this->cpp_type()); - } - - fn::GMutableSpan 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; - } - - 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(); }; @@ -336,36 +288,48 @@ class OutputAttribute { template<typename T> class OutputAttribute_Typed { private: OutputAttribute attribute_; - std::optional<fn::GVMutableArray_Typed<T>> optional_varray_; - VMutableArray<T> *varray_ = nullptr; + VMutableArray<T> varray_; public: + OutputAttribute_Typed(); OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute)) { if (attribute_) { - optional_varray_.emplace(attribute_.varray()); - varray_ = &**optional_varray_; + varray_ = attribute_.varray().template typed<T>(); } } + 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; + return varray_; } VMutableArray<T> &operator*() { - return *varray_; + return varray_; } VMutableArray<T> *operator->() { - return varray_; + return &varray_; } VMutableArray<T> &varray() { - return *varray_; + return varray_; } AttributeDomain domain() const @@ -394,6 +358,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. @@ -415,22 +386,28 @@ class CustomDataAttributes { CustomDataAttributes(CustomDataAttributes &&other); CustomDataAttributes &operator=(const CustomDataAttributes &other); - void reallocate(const int size); + void reallocate(int size); + + void clear(); std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const; - blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id, - const CustomDataType data_type, - const void *default_value) const; + /** + * Return a virtual array for a stored attribute, or a single value virtual array with the + * default value if the attribute doesn't exist. If no default value is provided, the default + * value for the type will be used. + */ + blender::fn::GVArray 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 AttributeIDRef &attribute_id, - const T &default_value) const + blender::VArray<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(attribute_id, type, &default_value); - return blender::fn::GVArray_Typed<T>(std::move(varray)); + GVArray varray = this->get_for_read(attribute_id, type, &default_value); + return varray.typed<T>(); } std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id); @@ -440,8 +417,149 @@ class CustomDataAttributes { void *buffer); bool remove(const AttributeIDRef &attribute_id); - bool foreach_attribute(const AttributeForeachCallback callback, - const AttributeDomain domain) const; + /** + * 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, 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(GVMutableArray 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_; +} + +inline GVMutableArray &OutputAttribute::operator*() +{ + return varray_; +} + +inline fn::GVMutableArray *OutputAttribute::operator->() +{ + return &varray_; +} + +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 diff --git a/source/blender/blenkernel/BKE_attribute_math.hh b/source/blender/blenkernel/BKE_attribute_math.hh index 2ce41e95b65..90f349125c9 100644 --- a/source/blender/blenkernel/BKE_attribute_math.hh +++ b/source/blender/blenkernel/BKE_attribute_math.hh @@ -18,8 +18,7 @@ #include "BLI_array.hh" #include "BLI_color.hh" -#include "BLI_float2.hh" -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "DNA_customdata_types.h" @@ -141,7 +140,7 @@ inline ColorGeometry4f mix3(const float3 &weights, * This is just basic linear interpolation. * \{ */ -template<typename T> T mix2(const float factor, const T &a, const T &b); +template<typename T> T mix2(float factor, const T &a, const T &b); template<> inline bool mix2(const float factor, const bool &a, const bool &b) { @@ -160,12 +159,12 @@ template<> inline float mix2(const float factor, const float &a, const float &b) template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b) { - return float2::interpolate(a, b, factor); + return math::interpolate(a, b, factor); } template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b) { - return float3::interpolate(a, b, factor); + return math::interpolate(a, b, factor); } template<> @@ -232,6 +231,43 @@ template<typename T> class SimpleMixer { }; /** + * Mixes together booleans with "or" while fitting the same interface as the other + * mixers in order to be simpler to use. This mixing method has a few benefits: + * - An "average" for selections is relatively meaningless. + * - Predictable selection propagation is very super important. + * - It's generally easier to remove an element from a selection that is slightly too large than + * the opposite. + */ +class BooleanPropagationMixer { + private: + MutableSpan<bool> buffer_; + + public: + /** + * \param buffer: Span where the interpolated values should be stored. + */ + BooleanPropagationMixer(MutableSpan<bool> buffer) : buffer_(buffer) + { + buffer_.fill(false); + } + + /** + * Mix a #value into the element with the given #index. + */ + void mix_in(const int64_t index, const bool value, [[maybe_unused]] const float weight = 1.0f) + { + buffer_[index] |= value; + } + + /** + * Does not do anything, since the mixing is trivial. + */ + void finalize() + { + } +}; + +/** * This mixer accumulates values in a type that is different from the one that is mixed. * Some types cannot encode the floating point weights in their values (e.g. int and bool). */ @@ -287,12 +323,12 @@ class ColorGeometryMixer { public: ColorGeometryMixer(MutableSpan<ColorGeometry4f> buffer, ColorGeometry4f default_color = ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f)); - void mix_in(const int64_t index, const ColorGeometry4f &color, const float weight = 1.0f); + void mix_in(int64_t index, const ColorGeometry4f &color, float weight = 1.0f); void finalize(); }; template<typename T> struct DefaultMixerStruct { - /* Use void by default. This can be check for in `if constexpr` statements. */ + /* Use void by default. This can be checked for in `if constexpr` statements. */ using type = void; }; template<> struct DefaultMixerStruct<float> { @@ -328,6 +364,23 @@ template<> struct DefaultMixerStruct<bool> { using type = SimpleMixerWithAccumulationType<bool, float, float_to_bool>; }; +template<typename T> struct DefaultPropatationMixerStruct { + /* Use void by default. This can be checked for in `if constexpr` statements. */ + using type = typename DefaultMixerStruct<T>::type; +}; + +template<> struct DefaultPropatationMixerStruct<bool> { + using type = BooleanPropagationMixer; +}; + +/** + * This mixer is meant for propagating attributes when creating new geometry. A key difference + * with the default mixer is that booleans are mixed with "or" instead of "at least half" + * (the default mixing for booleans). + */ +template<typename T> +using DefaultPropatationMixer = typename DefaultPropatationMixerStruct<T>::type; + /* Utility to get a good default mixer for a given type. This is `void` when there is no default * mixer for the given type. */ template<typename T> using DefaultMixer = typename DefaultMixerStruct<T>::type; diff --git a/source/blender/blenkernel/BKE_autoexec.h b/source/blender/blenkernel/BKE_autoexec.h index 84d83ae5d30..55bb3e8877f 100644 --- a/source/blender/blenkernel/BKE_autoexec.h +++ b/source/blender/blenkernel/BKE_autoexec.h @@ -23,6 +23,10 @@ extern "C" { #endif +/** + * \param path: The path to check against. + * \return Success + */ bool BKE_autoexec_match(const char *path); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_blender.h b/source/blender/blenkernel/BKE_blender.h index 813472715fa..8c0512edb1d 100644 --- a/source/blender/blenkernel/BKE_blender.h +++ b/source/blender/blenkernel/BKE_blender.h @@ -29,6 +29,9 @@ extern "C" { struct UserDef; +/** + * Only to be called on exit Blender. + */ void BKE_blender_free(void); void BKE_blender_globals_init(void); @@ -38,11 +41,19 @@ void BKE_blender_userdef_data_swap(struct UserDef *userdef_a, struct UserDef *us void BKE_blender_userdef_data_set(struct UserDef *userdef); void BKE_blender_userdef_data_set_and_free(struct UserDef *userdef); +/** + * Write U from userdef. + * This function defines which settings a template will override for the user preferences. + */ void BKE_blender_userdef_app_template_data_swap(struct UserDef *userdef_a, struct UserDef *userdef_b); void BKE_blender_userdef_app_template_data_set(struct UserDef *userdef); void BKE_blender_userdef_app_template_data_set_and_free(struct UserDef *userdef); +/** + * When loading a new userdef from file, + * or when exiting Blender. + */ void BKE_blender_userdef_data_free(struct UserDef *userdef, bool clear_fonts); /* Blenders' own atexit (avoids leaking) */ diff --git a/source/blender/blenkernel/BKE_blender_copybuffer.h b/source/blender/blenkernel/BKE_blender_copybuffer.h index 1dd6d495276..d1faf88a90c 100644 --- a/source/blender/blenkernel/BKE_blender_copybuffer.h +++ b/source/blender/blenkernel/BKE_blender_copybuffer.h @@ -30,19 +30,59 @@ struct Main; struct ReportList; struct bContext; -/* copybuffer (wrapper for BKE_blendfile_write_partial) */ -void BKE_copybuffer_begin(struct Main *bmain_src); -void BKE_copybuffer_tag_ID(struct ID *id); -bool BKE_copybuffer_save(struct Main *bmain_src, const char *filename, struct ReportList *reports); +/* Copy-buffer (wrapper for BKE_blendfile_write_partial). */ + +/** + * Initialize a copy operation. + */ +void BKE_copybuffer_copy_begin(struct Main *bmain_src); +/** + * Mark an ID to be copied. Should only be called after a call to #BKE_copybuffer_copy_begin. + */ +void BKE_copybuffer_copy_tag_ID(struct ID *id); +/** + * Finalize a copy operation into given .blend file 'buffer'. + * + * \param filename: Full path to the .blend file used as copy/paste buffer. + * + * \return true on success, false otherwise. + */ +bool BKE_copybuffer_copy_end(struct Main *bmain_src, + const char *filename, + struct ReportList *reports); +/** + * Paste data-blocks from the given .blend file 'buffer' (i.e. append them). + * + * Unlike #BKE_copybuffer_paste, it does not perform any instantiation of collections/objects/etc. + * + * \param libname: Full path to the .blend file used as copy/paste buffer. + * \param id_types_mask: Only directly link IDs of those types from the given .blend file buffer. + * + * \return true on success, false otherwise. + */ bool BKE_copybuffer_read(struct Main *bmain_dst, const char *libname, struct ReportList *reports, - const uint64_t id_types_mask); + uint64_t id_types_mask); +/** + * Paste data-blocks from the given .blend file 'buffer' (i.e. append them). + * + * Similar to #BKE_copybuffer_read, but also handles instantiation of collections/objects/etc. + * + * \param libname: Full path to the .blend file used as copy/paste buffer. + * \param flag: A combination of #eBLOLibLinkFlags and ##eFileSel_Params_Flag to control + * link/append behavior. + * \note Ignores #FILE_LINK flag, since it always appends IDs. + * \param id_types_mask: Only directly link IDs of those types from the given .blend file buffer. + * + * \return Number of IDs directly pasted from the buffer + * (does not includes indirectly linked ones). + */ int BKE_copybuffer_paste(struct bContext *C, const char *libname, - const short flag, + int flag, struct ReportList *reports, - const uint64_t id_types_mask); + uint64_t id_types_mask); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_blender_undo.h b/source/blender/blenkernel/BKE_blender_undo.h index 1febe75b6f2..d60d048ee5f 100644 --- a/source/blender/blenkernel/BKE_blender_undo.h +++ b/source/blender/blenkernel/BKE_blender_undo.h @@ -34,8 +34,8 @@ enum eUndoStepDir; struct MemFileUndoData *BKE_memfile_undo_encode(struct Main *bmain, struct MemFileUndoData *mfu_prev); bool BKE_memfile_undo_decode(struct MemFileUndoData *mfu, - const enum eUndoStepDir undo_direction, - const bool use_old_bmain_data, + enum eUndoStepDir undo_direction, + bool use_old_bmain_data, struct bContext *C); void BKE_memfile_undo_free(struct MemFileUndoData *mfu); diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index b04bbdfb187..d0ab8be9a29 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 31 +#define BLENDER_FILE_SUBVERSION 5 /* 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 300 -#define BLENDER_FILE_MIN_SUBVERSION 26 +#define BLENDER_FILE_MIN_SUBVERSION 42 /** User readable version string. */ const char *BKE_blender_version_string(void); diff --git a/source/blender/blenkernel/BKE_blendfile.h b/source/blender/blenkernel/BKE_blendfile.h index 3e0a343a766..db86d4685b7 100644 --- a/source/blender/blenkernel/BKE_blendfile.h +++ b/source/blender/blenkernel/BKE_blendfile.h @@ -33,12 +33,20 @@ struct ReportList; struct UserDef; struct bContext; +/** + * Shared setup function that makes the data from `bfd` into the current blend file, + * replacing the contents of #G.main. + * This uses the bfd #BKE_blendfile_read and similarly named functions. + * + * This is done in a separate step so the caller may perform actions after it is known the file + * loaded correctly but before the file replaces the existing blend file contents. + */ void BKE_blendfile_read_setup_ex(struct bContext *C, struct BlendFileData *bfd, const struct BlendFileReadParams *params, struct BlendFileReadReport *reports, /* Extra args. */ - const bool startup_update_defaults, + bool startup_update_defaults, const char *startup_app_template); void BKE_blendfile_read_setup(struct bContext *C, @@ -46,28 +54,56 @@ void BKE_blendfile_read_setup(struct bContext *C, const struct BlendFileReadParams *params, struct BlendFileReadReport *reports); +/** + * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + */ struct BlendFileData *BKE_blendfile_read(const char *filepath, const struct BlendFileReadParams *params, struct BlendFileReadReport *reports); +/** + * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + */ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, int filelength, const struct BlendFileReadParams *params, struct ReportList *reports); +/** + * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * \note `memfile` is the undo buffer. + */ struct BlendFileData *BKE_blendfile_read_from_memfile(struct Main *bmain, struct MemFile *memfile, const struct BlendFileReadParams *params, struct ReportList *reports); +/** + * Utility to make a file 'empty' used for startup to optionally give an empty file. + * Handy for tests. + */ void BKE_blendfile_read_make_empty(struct bContext *C); +/** + * Only read the #UserDef from a .blend. + */ struct UserDef *BKE_blendfile_userdef_read(const char *filepath, struct ReportList *reports); struct UserDef *BKE_blendfile_userdef_read_from_memory(const void *filebuf, int filelength, struct ReportList *reports); struct UserDef *BKE_blendfile_userdef_from_defaults(void); +/** + * Only write the #UserDef in a `.blend`. + * \return success. + */ bool BKE_blendfile_userdef_write(const char *filepath, struct ReportList *reports); +/** + * Only write the #UserDef in a `.blend`, merging with the existing blend file. + * \return success. + * + * \note In the future we should re-evaluate user preferences, + * possibly splitting out system/hardware specific preferences. + */ bool BKE_blendfile_userdef_write_app_template(const char *filepath, struct ReportList *reports); bool BKE_blendfile_userdef_write_all(struct ReportList *reports); @@ -81,13 +117,18 @@ bool BKE_blendfile_workspace_config_write(struct Main *bmain, struct ReportList *reports); void BKE_blendfile_workspace_config_data_free(struct WorkspaceConfigFileData *workspace_config); -/* partial blend file writing */ +/* Partial blend file writing. */ + void BKE_blendfile_write_partial_tag_ID(struct ID *id, bool set); void BKE_blendfile_write_partial_begin(struct Main *bmain_src); +/** + * \param remap_mode: Choose the kind of path remapping or none #eBLO_WritePathRemap. + * \return Success. + */ bool BKE_blendfile_write_partial(struct Main *bmain_src, const char *filepath, - const int write_flags, - const int remap_mode, + int write_flags, + int remap_mode, struct ReportList *reports); void BKE_blendfile_write_partial_end(struct Main *bmain_src); diff --git a/source/blender/blenkernel/BKE_blendfile_link_append.h b/source/blender/blenkernel/BKE_blendfile_link_append.h new file mode 100644 index 00000000000..983c93223a1 --- /dev/null +++ b/source/blender/blenkernel/BKE_blendfile_link_append.h @@ -0,0 +1,222 @@ +/* + * 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 + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct BlendHandle; +struct ID; +struct Library; +struct LibraryLink_Params; +struct Main; +struct ReportList; +struct Scene; +struct View3D; +struct ViewLayer; + +typedef struct BlendfileLinkAppendContext BlendfileLinkAppendContext; +typedef struct BlendfileLinkAppendContextItem BlendfileLinkAppendContextItem; + +/** + * Allocate and initialize a new context to link/append data-blocks. + */ +BlendfileLinkAppendContext *BKE_blendfile_link_append_context_new( + struct LibraryLink_Params *params); +/** + * Free a link/append context. + */ +void BKE_blendfile_link_append_context_free(struct BlendfileLinkAppendContext *lapp_context); +/** + * Set or clear flags in given \a lapp_context. + * + * \param flag: A combination of: + * - #eFileSel_Params_Flag from `DNA_space_types.h` & + * - #eBLOLibLinkFlags * from `BLO_readfile.h`. + * \param do_set: Set the given \a flag if true, clear it otherwise. + */ +void BKE_blendfile_link_append_context_flag_set(struct BlendfileLinkAppendContext *lapp_context, + int flag, + bool do_set); + +/** + * Store reference to a Blender's embedded memfile into the context. + * + * \note This is required since embedded startup blender file is handled in `ED` module, which + * cannot be linked in BKE code. + */ +void BKE_blendfile_link_append_context_embedded_blendfile_set( + struct BlendfileLinkAppendContext *lapp_context, + const void *blendfile_mem, + int blendfile_memsize); +/** Clear reference to Blender's embedded startup file into the context. */ +void BKE_blendfile_link_append_context_embedded_blendfile_clear( + struct BlendfileLinkAppendContext *lapp_context); + +/** + * Add a new source library to search for items to be linked to the given link/append context. + * + * \param libname: the absolute path to the library blend file. + * \param blo_handle: the blend file handle of the library, NULL is not available. Note that this + * is only borrowed for linking purpose, no releasing or other management will + * be performed by #BKE_blendfile_link_append code on it. + * + * \note *Never* call #BKE_blendfile_link_append_context_library_add() + * after having added some items. + */ +void BKE_blendfile_link_append_context_library_add(struct BlendfileLinkAppendContext *lapp_context, + const char *libname, + struct BlendHandle *blo_handle); +/** + * Add a new item (data-block name and `idcode`) to be searched and linked/appended from libraries + * associated to the given context. + * + * \param userdata: an opaque user-data pointer stored in generated link/append item. + * + * TODO: Add a more friendly version of this that combines it with the call to + * #BKE_blendfile_link_append_context_item_library_index_enable to enable the added item for all + * added library sources. + */ +struct BlendfileLinkAppendContextItem *BKE_blendfile_link_append_context_item_add( + struct BlendfileLinkAppendContext *lapp_context, + const char *idname, + short idcode, + void *userdata); + +#define BLENDFILE_LINK_APPEND_INVALID -1 +/** + * Search for all ID matching given `id_types_filter` in given `library_index`, and add them to + * the list of items to process. + * + * \note #BKE_blendfile_link_append_context_library_add should never be called on the same + *`lapp_context` after this function. + * + * \param id_types_filter: A set of `FILTER_ID` bitflags, the types of IDs to add to the items + * list. + * \param library_index: The index of the library to look into, in given `lapp_context`. + * + * \return The number of items found and added to the list, or `BLENDFILE_LINK_APPEND_INVALID` if + * it could not open the .blend file. + */ +int BKE_blendfile_link_append_context_item_idtypes_from_library_add( + struct BlendfileLinkAppendContext *lapp_context, + struct ReportList *reports, + uint64_t id_types_filter, + int library_index); + +/** + * Enable search of the given \a item into the library stored at given index in the link/append + * context. + */ +void BKE_blendfile_link_append_context_item_library_index_enable( + struct BlendfileLinkAppendContext *lapp_context, + struct BlendfileLinkAppendContextItem *item, + int library_index); +/** + * Check if given link/append context is empty (has no items to process) or not. + */ +bool BKE_blendfile_link_append_context_is_empty(struct BlendfileLinkAppendContext *lapp_context); + +void *BKE_blendfile_link_append_context_item_userdata_get( + struct BlendfileLinkAppendContext *lapp_context, struct BlendfileLinkAppendContextItem *item); +struct ID *BKE_blendfile_link_append_context_item_newid_get( + struct BlendfileLinkAppendContext *lapp_context, struct BlendfileLinkAppendContextItem *item); +short BKE_blendfile_link_append_context_item_idcode_get( + struct BlendfileLinkAppendContext *lapp_context, struct BlendfileLinkAppendContextItem *item); + +typedef enum eBlendfileLinkAppendForeachItemFlag { + /** Loop over directly linked items (i.e. those explicitly defined by user code). */ + BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_DIRECT = 1 << 0, + /** Loop over indirectly linked items (i.e. those defined by internal code, as dependencies of + * direct ones). + * + * IMPORTANT: Those 'indirect' items currently may not cover **all** indirectly linked data. + * See comments in #foreach_libblock_link_append_callback. */ + BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_INDIRECT = 1 << 1, +} eBlendfileLinkAppendForeachItemFlag; +/** + * Callback called by #BKE_blendfile_link_append_context_item_foreach over each (or a subset of + * each) of the items in given #BlendfileLinkAppendContext. + * + * \param userdata: An opaque void pointer passed to the `callback_function`. + * + * \return `true` if iteration should continue, `false` otherwise. + */ +typedef bool (*BKE_BlendfileLinkAppendContexteItemFunction)( + struct BlendfileLinkAppendContext *lapp_context, + struct BlendfileLinkAppendContextItem *item, + void *userdata); +/** + * Iterate over all (or a subset) of the items listed in given #BlendfileLinkAppendContext, + * and call the `callback_function` on them. + * + * \param flag: Control which type of items to process (see + * #eBlendfileLinkAppendForeachItemFlag enum flags). + * \param userdata: An opaque void pointer passed to the `callback_function`. + */ +void BKE_blendfile_link_append_context_item_foreach( + struct BlendfileLinkAppendContext *lapp_context, + BKE_BlendfileLinkAppendContexteItemFunction callback_function, + eBlendfileLinkAppendForeachItemFlag flag, + void *userdata); + +/** + * Perform append operation, using modern ID usage looper to detect which ID should be kept + * linked, made local, duplicated as local, re-used from local etc. + * + * The IDs processed by this functions are the one that have been linked by a previous call to + * #BKE_blendfile_link on the same `lapp_context`. + */ +void BKE_blendfile_append(struct BlendfileLinkAppendContext *lapp_context, + struct ReportList *reports); +/** + * Perform linking operation on all items added to given `lapp_context`. + */ +void BKE_blendfile_link(struct BlendfileLinkAppendContext *lapp_context, + struct ReportList *reports); + +/** + * Try to relocate all linked IDs added to `lapp_context`, belonging to the given `library`. + * + * This function searches for matching IDs (type and name) in all libraries added to the given + * `lapp_context`. + * + * Typical usages include: + * - Relocating a library: + * - Add the new target library path to `lapp_context`. + * - Add all IDs from the library to relocate to `lapp_context` + * - Mark the new target library to be considered for each ID. + * - Call this function. + * + * - Searching for (e.g.missing) linked IDs in a set or sub-set of libraries: + * - Add all potential library sources paths to `lapp_context`. + * - Add all IDs to search for to `lapp_context`. + * - Mark which libraries should be considered for each ID. + * - Call this function. + */ +void BKE_blendfile_library_relocate(struct BlendfileLinkAppendContext *lapp_context, + struct ReportList *reports, + struct Library *library, + bool do_reload); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_boids.h b/source/blender/blenkernel/BKE_boids.h index 71a4d35767f..a9c8ad6422f 100644 --- a/source/blender/blenkernel/BKE_boids.h +++ b/source/blender/blenkernel/BKE_boids.h @@ -51,7 +51,13 @@ typedef struct BoidBrainData { } BoidBrainData; void boids_precalc_rules(struct ParticleSettings *part, float cfra); +/** + * Determines the velocity the boid wants to have. + */ void boid_brain(BoidBrainData *bbd, int p, struct ParticleData *pa); +/** + * Tries to realize the wanted velocity taking all constraints into account. + */ void boid_body(BoidBrainData *bbd, struct ParticleData *pa); void boid_default_settings(struct BoidSettings *boids); struct BoidRule *boid_new_rule(int type); diff --git a/source/blender/blenkernel/BKE_bpath.h b/source/blender/blenkernel/BKE_bpath.h index 3ec5409ca7f..ccbd0d4cbe4 100644 --- a/source/blender/blenkernel/BKE_bpath.h +++ b/source/blender/blenkernel/BKE_bpath.h @@ -16,10 +16,14 @@ /** \file * \ingroup bke - * \attention Based on ghash, difference is ghash is not a fixed size, - * so for BPath we don't need to malloc + * + * \warning All paths manipulated by this API are assumed to be either constant char buffers of + * `FILE_MAX` size, or allocated char buffers not bigger than `FILE_MAX`. */ +/* TODO: Make this module handle a bit more safely string length, instead of assuming buffers are + * FILE_MAX length etc. */ + #pragma once #ifdef __cplusplus @@ -31,66 +35,181 @@ struct ListBase; struct Main; struct ReportList; -/* Function that does something with an ID's file path. Should return 1 if the - * path has changed, and in that case, should write the result to pathOut. */ -typedef bool (*BPathVisitor)(void *userdata, char *path_dst, const char *path_src); -/* Executes 'visit' for each path associated with 'id'. */ -void BKE_bpath_traverse_id(struct Main *bmain, - struct ID *id, - BPathVisitor visit_cb, - const int flag, - void *bpath_user_data); -void BKE_bpath_traverse_id_list(struct Main *bmain, - struct ListBase *lb, - BPathVisitor visit_cb, - const int flag, - void *bpath_user_data); -void BKE_bpath_traverse_main(struct Main *bmain, - BPathVisitor visit_cb, - const int flag, - void *bpath_user_data); -bool BKE_bpath_relocate_visitor(void *oldbasepath, char *path_dst, const char *path_src); - -/* Functions for temp backup/restore of paths, path count must NOT change */ -void *BKE_bpath_list_backup(struct Main *bmain, const int flag); -void BKE_bpath_list_restore(struct Main *bmain, const int flag, void *ls_handle); -void BKE_bpath_list_free(void *ls_handle); - -enum { - /* convert paths to absolute */ - BKE_BPATH_TRAVERSE_ABS = (1 << 0), - /* skip library paths */ - BKE_BPATH_TRAVERSE_SKIP_LIBRARY = (1 << 1), - /* skip packed data */ - BKE_BPATH_TRAVERSE_SKIP_PACKED = (1 << 2), - /* skip paths where a single dir is used with an array of files, eg. - * sequence strip images and pointcache. in this case only use the first - * file, this is needed for directory manipulation functions which might - * otherwise modify the same directory multiple times */ - BKE_BPATH_TRAVERSE_SKIP_MULTIFILE = (1 << 3), - /* reload data (when the path is edited) */ - BKE_BPATH_TRAVERSE_RELOAD_EDITED = (1 << 4), -}; - -/* high level funcs */ - -/* creates a text file with missing files if there are any */ +/** \name Core `foreach_path` API. + * \{ */ + +typedef enum eBPathForeachFlag { + /** Flags controlling the behavior of the generic BPath API. */ + + /** Ensures the `absolute_base_path` member of #BPathForeachPathData is initialized properly with + * the path of the current .blend file. This can be used by the callbacks to convert relative + * paths to absolute ones. */ + BKE_BPATH_FOREACH_PATH_ABSOLUTE = (1 << 0), + /** Skip paths of linked IDs. */ + BKE_BPATH_FOREACH_PATH_SKIP_LINKED = (1 << 1), + /** Skip paths when their matching data is packed. */ + BKE_BPATH_FOREACH_PATH_SKIP_PACKED = (1 << 2), + /** Resolve tokens within a virtual filepath to a single, concrete, filepath. */ + BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN = (1 << 3), + /* Skip weak reference paths. Those paths are typically 'nice to have' extra information, but are + * not used as actual source of data by the current .blend file. + * + * NOTE: Currently this only concerns the weak reference to a library file stored in + * `ID::library_weak_reference`. */ + BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES = (1 << 5), + + /** Flags not affecting the generic BPath API. Those may be used by specific IDTypeInfo + * `foreach_path` implementations and/or callbacks to implement specific behaviors. */ + + /** Skip paths where a single dir is used with an array of files, eg. sequence strip images or + * point-caches. In this case only use the first file path is processed. + * + * This is needed for directory manipulation callbacks which might otherwise modify the same + * directory multiple times. */ + BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE = (1 << 8), + /** Reload data (when the path is edited). + * \note Only used by Image IDType currently. */ + BKE_BPATH_FOREACH_PATH_RELOAD_EDITED = (1 << 9), +} eBPathForeachFlag; + +struct BPathForeachPathData; + +/** Callback used to iterate over an ID's file paths. + * + * \note `path`s parameters should be considered as having a maximal `FILE_MAX` string length. + * + * \return `true` if the path has been changed, and in that case, result should be written into + * `r_path_dst`. */ +typedef bool (*BPathForeachPathFunctionCallback)(struct BPathForeachPathData *bpath_data, + char *r_path_dst, + const char *path_src); + +/** Storage for common data needed across the BPath 'foreach_path' code. */ +typedef struct BPathForeachPathData { + struct Main *bmain; + + BPathForeachPathFunctionCallback callback_function; + eBPathForeachFlag flag; + + void *user_data; + + /* 'Private' data, caller don't need to set those. */ + + /** The root to use as base for relative paths. Only set if `BKE_BPATH_FOREACH_PATH_ABSOLUTE` + * flag is set, NULL otherwise. */ + const char *absolute_base_path; +} BPathForeachPathData; + +/** Run `bpath_data.callback_function` on all paths contained in `id`. */ +void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, struct ID *id); + +/** Run `bpath_data.callback_function` on all paths of all IDs in `bmain`. */ +void BKE_bpath_foreach_path_main(BPathForeachPathData *bpath_data); + +/** \} */ + +/** \name Helpers to handle common cases from `IDTypeInfo`'s `foreach_path` functions. + * \{ */ + +/* TODO: Investigate using macros around those calls to check a bit better about actual + * strings/buffers length (e,g, with static asserts). */ + +/** + * Run the callback on a path, replacing the content of the string as needed. + * + * \param path: A fixed, FILE_MAX-sized char buffer. + * + * \return true is \a path was modified, false otherwise. + */ +bool BKE_bpath_foreach_path_fixed_process(struct BPathForeachPathData *bpath_data, char *path); + +/** + * Run the callback on a (directory + file) path, replacing the content of the two strings as + * needed. + * + * \param path_dir: A fixed, FILE_MAXDIR-sized char buffer. + * \param path_file: A fixed, FILE_MAXFILE-sized char buffer. + * + * \return true is \a path_dir and/or \a path_file were modified, false otherwise. + */ +bool BKE_bpath_foreach_path_dirfile_fixed_process(struct BPathForeachPathData *bpath_data, + char *path_dir, + char *path_file); + +/** + * Run the callback on a path, replacing the content of the string as needed. + * + * \param path: A pointer to a MEM-allocated string. If modified, it will be freed and replaced by + * a new allocated string. + * \note path is expected to be FILE_MAX size or smaller. + * + * \return true is \a path was modified and re-allocated, false otherwise. + */ +bool BKE_bpath_foreach_path_allocated_process(struct BPathForeachPathData *bpath_data, + char **path); + +/** \} */ + +/** \name High level features. + * \{ */ + +/** Check for missing files. */ void BKE_bpath_missing_files_check(struct Main *bmain, struct ReportList *reports); + +/** Recursively search into given search directory, for all file paths of all IDs in given \a + * bmain, and replace existing paths as needed. + * + * \note The search will happen into the whole search directory tree recursively (with a limit of + * MAX_DIR_RECURSE), if several files are found matching a searched filename, the biggest one will + * be used. This is so that things like thumbnails don't get selected instead of the actual image + * e.g. + * + * \param searchpath: The root directory in which the new filepaths should be searched for. + * \param find_all: If `true`, also search for files which current path is still valid, if `false` + * skip those still valid paths. + * */ void BKE_bpath_missing_files_find(struct Main *bmain, const char *searchpath, struct ReportList *reports, - const bool find_all); + bool find_all); + +/** Rebase all relative file paths in given \a bmain from \a basedir_src to \a basedir_dst. */ void BKE_bpath_relative_rebase(struct Main *bmain, const char *basedir_src, const char *basedir_dst, struct ReportList *reports); + +/** Make all absolute file paths in given \a bmain relative to given \a basedir. */ void BKE_bpath_relative_convert(struct Main *bmain, const char *basedir, struct ReportList *reports); + +/** Make all relative file paths in given \a bmain absolute, using given \a basedir as root. */ void BKE_bpath_absolute_convert(struct Main *bmain, const char *basedir, struct ReportList *reports); +/** Temp backup of paths from all IDs in given \a bmain. + * + * \return An opaque handle to pass to #BKE_bpath_list_restore and #BKE_bpath_list_free. + */ +void *BKE_bpath_list_backup(struct Main *bmain, eBPathForeachFlag flag); + +/** Restore the temp backup of paths from \a path_list_handle into all IDs in given \a bmain. + * + * \note This function assumes that the data in given Main did not change (no + * addition/deletion/re-ordering of IDs, or their file paths) since the call to + * #BKE_bpath_list_backup that generated the given \a path_list_handle. */ +void BKE_bpath_list_restore(struct Main *bmain, eBPathForeachFlag flag, void *path_list_handle); + +/** Free the temp backup of paths in \a path_list_handle. + * + * \note This function assumes that the path list has already been restored with a call to + * #BKE_bpath_list_restore, and is therefore empty. */ +void BKE_bpath_list_free(void *path_list_handle); + +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h index 452a08bc9c8..ee5ab905d70 100644 --- a/source/blender/blenkernel/BKE_brush.h +++ b/source/blender/blenkernel/BKE_brush.h @@ -39,70 +39,109 @@ struct UnifiedPaintSettings; // enum eCurveMappingPreset; -/* globals for brush execution */ +/* Globals for brush execution. */ + void BKE_brush_system_init(void); void BKE_brush_system_exit(void); -/* datablock functions */ -struct Brush *BKE_brush_add(struct Main *bmain, const char *name, const eObjectMode ob_mode); +/* Data-block functions. */ + +/** + * \note Resulting brush will have two users: one as a fake user, + * another is assumed to be used by the caller. + */ +struct Brush *BKE_brush_add(struct Main *bmain, const char *name, eObjectMode ob_mode); +/** + * Add a new gp-brush. + */ struct Brush *BKE_brush_add_gpencil(struct Main *bmain, struct ToolSettings *ts, const char *name, eObjectMode mode); +/** + * Delete a Brush. + */ bool BKE_brush_delete(struct Main *bmain, struct Brush *brush); +/** + * Add grease pencil settings. + */ void BKE_brush_init_gpencil_settings(struct Brush *brush); -struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode); +struct Brush *BKE_brush_first_search(struct Main *bmain, eObjectMode ob_mode); void BKE_brush_sculpt_reset(struct Brush *brush); -void BKE_brush_gpencil_paint_presets(struct Main *bmain, - struct ToolSettings *ts, - const bool reset); -void BKE_brush_gpencil_vertex_presets(struct Main *bmain, - struct ToolSettings *ts, - const bool reset); -void BKE_brush_gpencil_sculpt_presets(struct Main *bmain, - struct ToolSettings *ts, - const bool reset); -void BKE_brush_gpencil_weight_presets(struct Main *bmain, - struct ToolSettings *ts, - const bool reset); -void BKE_gpencil_brush_preset_set(struct Main *bmain, struct Brush *brush, const short type); - -/* jitter */ +/** + * Create a set of grease pencil Drawing presets. + */ +void BKE_brush_gpencil_paint_presets(struct Main *bmain, struct ToolSettings *ts, bool reset); +/** + * Create a set of grease pencil Vertex Paint presets. + */ +void BKE_brush_gpencil_vertex_presets(struct Main *bmain, struct ToolSettings *ts, bool reset); +/** + * Create a set of grease pencil Sculpt Paint presets. + */ +void BKE_brush_gpencil_sculpt_presets(struct Main *bmain, struct ToolSettings *ts, bool reset); +/** + * Create a set of grease pencil Weight Paint presets. + */ +void BKE_brush_gpencil_weight_presets(struct Main *bmain, struct ToolSettings *ts, bool reset); +void BKE_gpencil_brush_preset_set(struct Main *bmain, struct Brush *brush, short type); + void BKE_brush_jitter_pos(const struct Scene *scene, struct Brush *brush, const float pos[2], float jitterpos[2]); void BKE_brush_randomize_texture_coords(struct UnifiedPaintSettings *ups, bool mask); -/* brush curve */ +/* Brush curve. */ + +/** + * Library Operations + */ void BKE_brush_curve_preset(struct Brush *b, enum eCurveMappingPreset preset); -float BKE_brush_curve_strength_clamped(struct Brush *br, float p, const float len); -float BKE_brush_curve_strength(const struct Brush *br, float p, const float len); +/** + * Uses the brush curve control to find a strength value between 0 and 1. + */ +float BKE_brush_curve_strength_clamped(const struct Brush *br, float p, float len); +/** + * Uses the brush curve control to find a strength value. + */ +float BKE_brush_curve_strength(const struct Brush *br, float p, float len); + +/* Sampling. */ -/* sampling */ +/** + * Generic texture sampler for 3D painting systems. + * point has to be either in region space mouse coordinates, + * or 3d world coordinates for 3D mapping. + * + * RGBA outputs straight alpha. + */ float BKE_brush_sample_tex_3d(const struct Scene *scene, const struct Brush *br, const float point[3], float rgba[4], - const int thread, + int thread, struct ImagePool *pool); float BKE_brush_sample_masktex(const struct Scene *scene, struct Brush *br, const float point[2], - const int thread, + int thread, struct ImagePool *pool); -/* texture */ +/* Texture. */ + unsigned int *BKE_brush_gen_texture_cache(struct Brush *br, int half_side, bool use_secondary); -/* radial control */ +/** + * Radial control. + */ struct ImBuf *BKE_brush_gen_radial_control_imbuf(struct Brush *br, bool secondary, bool display_gradient); -/* unified strength size and color */ +/* Unified strength size and color. */ const float *BKE_brush_color_get(const struct Scene *scene, const struct Brush *brush); const float *BKE_brush_secondary_color_get(const struct Scene *scene, const struct Brush *brush); @@ -127,12 +166,16 @@ bool BKE_brush_use_size_pressure(const struct Brush *brush); bool BKE_brush_sculpt_has_secondary_color(const struct Brush *brush); -/* scale unprojected radius to reflect a change in the brush's 2D size */ +/** + * Scale unprojected radius to reflect a change in the brush's 2D size. + */ void BKE_brush_scale_unprojected_radius(float *unprojected_radius, int new_brush_size, int old_brush_size); -/* scale brush size to reflect a change in the brush's unprojected radius */ +/** + * Scale brush size to reflect a change in the brush's unprojected radius. + */ void BKE_brush_scale_size(int *r_brush_size, float new_unprojected_radius, float old_unprojected_radius); diff --git a/source/blender/blenkernel/BKE_bvhutils.h b/source/blender/blenkernel/BKE_bvhutils.h index 06be8ec80fc..c454e441551 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,19 @@ 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 instant access to data. */ const struct MVert *vert; - const struct MEdge *edge; /* only used for BVHTreeFromMeshEdges */ + const float (*vert_normals)[3]; + const struct MEdge *edge; /* only used for #BVHTreeFromMeshEdges */ const struct MFace *face; const struct MLoop *loop; const struct MLoopTri *looptri; @@ -105,7 +106,7 @@ typedef enum BVHCacheType { } BVHCacheType; /** - * Builds a bvh tree where nodes are the relevant elements of the given mesh. + * Builds a BVH tree where nodes are the relevant elements of the given mesh. * Configures #BVHTreeFromMesh. * * The tree is build in mesh space coordinates, this means special care must be made on queries @@ -113,11 +114,14 @@ typedef enum BVHCacheType { * Reason for this is that bvh_from_mesh_* can use a cache in some cases and so it * becomes possible to reuse a #BVHTree. * - * free_bvhtree_from_mesh should be called when the tree is no longer needed. + * #free_bvhtree_from_mesh should be called when the tree is no longer needed. */ BVHTree *bvhtree_from_editmesh_verts( BVHTreeFromEditMesh *data, struct BMEditMesh *em, float epsilon, int tree_type, int axis); +/** + * Builds a BVH-tree where nodes are the vertices of the given `em`. + */ BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data, struct BMEditMesh *em, const BLI_bitmap *mask, @@ -125,26 +129,36 @@ BVHTree *bvhtree_from_editmesh_verts_ex(BVHTreeFromEditMesh *data, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); +/** + * 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 + * (else will be computed from mask). + */ BVHTree *bvhtree_from_mesh_verts_ex(struct BVHTreeFromMesh *data, const struct MVert *vert, - const int verts_num, - const bool vert_allocated, - const BLI_bitmap *mask, + int verts_num, + bool vert_allocated, + const BLI_bitmap *verts_mask, int verts_num_active, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); BVHTree *bvhtree_from_editmesh_edges( BVHTreeFromEditMesh *data, struct BMEditMesh *em, float epsilon, int tree_type, int axis); +/** + * Builds a BVH-tree where nodes are the edges of the given `em`. + */ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data, struct BMEditMesh *em, const BLI_bitmap *edges_mask, @@ -152,43 +166,63 @@ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); +/** + * 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 + * (else will be computed from mask). + */ BVHTree *bvhtree_from_mesh_edges_ex(struct BVHTreeFromMesh *data, const struct MVert *vert, - const bool vert_allocated, + bool vert_allocated, const struct MEdge *edge, - const int edges_num, - const bool edge_allocated, + int edges_num, + bool edge_allocated, const BLI_bitmap *edges_mask, int edges_num_active, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); +/** + * 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 + * (else will be computed from mask). + */ BVHTree *bvhtree_from_mesh_faces_ex(struct BVHTreeFromMesh *data, const struct MVert *vert, - const bool vert_allocated, + bool vert_allocated, const struct MFace *face, - const int numFaces, - const bool face_allocated, - const BLI_bitmap *mask, + int numFaces, + bool face_allocated, + const BLI_bitmap *faces_mask, int faces_num_active, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); BVHTree *bvhtree_from_editmesh_looptri( BVHTreeFromEditMesh *data, struct BMEditMesh *em, float epsilon, int tree_type, int axis); +/** + * Builds a BVH-tree where nodes are the `looptri` faces of the given `bm`. + */ BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data, struct BMEditMesh *em, const BLI_bitmap *mask, @@ -196,56 +230,70 @@ BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); +/** + * Builds a BVH-tree where nodes are the looptri faces of the given mesh. + * + * \note for edit-mesh this is currently a duplicate of #bvhtree_from_mesh_faces_ex + */ BVHTree *bvhtree_from_mesh_looptri_ex(struct BVHTreeFromMesh *data, const struct MVert *vert, - const bool vert_allocated, + bool vert_allocated, const struct MLoop *mloop, - const bool loop_allocated, + bool loop_allocated, const struct MLoopTri *looptri, - const int looptri_num, - const bool looptri_allocated, + int looptri_num, + bool looptri_allocated, const BLI_bitmap *mask, int looptri_num_active, float epsilon, int tree_type, int axis, - const BVHCacheType bvh_cache_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); +/** + * Builds or queries a BVH-cache for the cache BVH-tree 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, const struct Mesh *mesh, - const BVHCacheType bvh_cache_type, - const int tree_type); + BVHCacheType bvh_cache_type, + int tree_type); +/** + * Builds or queries a BVH-cache for the cache BVH-tree of the request type. + */ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data, struct BMEditMesh *em, - const int tree_type, - const BVHCacheType bvh_cache_type, + int tree_type, + BVHCacheType bvh_cache_type, struct BVHCache **bvh_cache_p, ThreadMutex *mesh_eval_mutex); /** - * Frees data allocated by a call to bvhtree_from_mesh_*. + * Frees data allocated by a call to `bvhtree_from_editmesh_*`. */ void free_bvhtree_from_editmesh(struct BVHTreeFromEditMesh *data); +/** + * Frees data allocated by a call to `bvhtree_from_mesh_*`. + */ void free_bvhtree_from_mesh(struct BVHTreeFromMesh *data); /** * Math functions used by callbacks */ -float bvhtree_ray_tri_intersection(const BVHTreeRay *ray, - const float m_dist, - const float v0[3], - const float v1[3], - const float v2[3]); +float bvhtree_ray_tri_intersection( + const BVHTreeRay *ray, float m_dist, const float v0[3], const float v1[3], const float v2[3]); float bvhtree_sphereray_tri_intersection(const BVHTreeRay *ray, float radius, - const float m_dist, + float m_dist, const float v0[3], const float v1[3], const float v2[3]); @@ -260,7 +308,7 @@ typedef struct BVHTreeFromPointCloud { BVHTree *BKE_bvhtree_from_pointcloud_get(struct BVHTreeFromPointCloud *data, const struct PointCloud *pointcloud, - const int tree_type); + int tree_type); void free_bvhtree_from_pointcloud(struct BVHTreeFromPointCloud *data); @@ -272,6 +320,9 @@ void free_bvhtree_from_pointcloud(struct BVHTreeFromPointCloud *data); bool bvhcache_has_tree(const struct BVHCache *bvh_cache, const BVHTree *tree); struct BVHCache *bvhcache_init(void); +/** + * Frees a BVH-cache. + */ void bvhcache_free(struct BVHCache *bvh_cache); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_cachefile.h b/source/blender/blenkernel/BKE_cachefile.h index 836597f95da..c6821d88d2a 100644 --- a/source/blender/blenkernel/BKE_cachefile.h +++ b/source/blender/blenkernel/BKE_cachefile.h @@ -28,6 +28,7 @@ extern "C" { #endif struct CacheFile; +struct CacheFileLayer; struct CacheReader; struct Depsgraph; struct Main; @@ -50,9 +51,7 @@ bool BKE_cachefile_filepath_get(const struct Main *bmain, const struct CacheFile *cache_file, char r_filename[1024]); -float BKE_cachefile_time_offset(const struct CacheFile *cache_file, - const float time, - const float fps); +float BKE_cachefile_time_offset(const struct CacheFile *cache_file, float time, float fps); /* Modifiers and constraints open and free readers through these. */ void BKE_cachefile_reader_open(struct CacheFile *cache_file, @@ -61,9 +60,24 @@ 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); +/** + * 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 struct CacheFile *cache_file, struct Scene *scene, - const int dag_eval_mode); + int dag_eval_mode); + +/* Add a layer to the cache_file. Return NULL if the filename is already that of an existing layer + * or if the number of layers exceeds the maximum allowed layer count. */ +struct CacheFileLayer *BKE_cachefile_add_layer(struct CacheFile *cache_file, + const char filename[1024]); + +struct CacheFileLayer *BKE_cachefile_get_active_layer(struct CacheFile *cache_file); + +void BKE_cachefile_remove_layer(struct CacheFile *cache_file, struct CacheFileLayer *layer); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_callbacks.h b/source/blender/blenkernel/BKE_callbacks.h index 7c518f33c89..2e4923ff3d2 100644 --- a/source/blender/blenkernel/BKE_callbacks.h +++ b/source/blender/blenkernel/BKE_callbacks.h @@ -114,14 +114,14 @@ typedef enum { typedef struct bCallbackFuncStore { struct bCallbackFuncStore *next, *prev; - void (*func)(struct Main *, struct PointerRNA **, const int num_pointers, void *arg); + void (*func)(struct Main *, struct PointerRNA **, int num_pointers, void *arg); void *arg; short alloc; } bCallbackFuncStore; void BKE_callback_exec(struct Main *bmain, struct PointerRNA **pointers, - const int num_pointers, + int num_pointers, eCbEvent evt); void BKE_callback_exec_null(struct Main *bmain, eCbEvent evt); void BKE_callback_exec_id(struct Main *bmain, struct ID *id, eCbEvent evt); @@ -133,6 +133,9 @@ void BKE_callback_add(bCallbackFuncStore *funcstore, eCbEvent evt); void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt); void BKE_callback_global_init(void); +/** + * Call on application exit. + */ void BKE_callback_global_finalize(void); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_camera.h b/source/blender/blenkernel/BKE_camera.h index b42fcbe7808..ee78621c11f 100644 --- a/source/blender/blenkernel/BKE_camera.h +++ b/source/blender/blenkernel/BKE_camera.h @@ -37,22 +37,26 @@ struct Scene; struct View3D; struct rctf; -/* Camera Datablock */ +/* Camera Data-block */ void *BKE_camera_add(struct Main *bmain, const char *name); /* Camera Usage */ +/** + * Get the camera's DOF value, takes the DOF object into account. + */ float BKE_camera_object_dof_distance(struct Object *ob); int BKE_camera_sensor_fit(int sensor_fit, float sizex, float sizey); float BKE_camera_sensor_size(int sensor_fit, float sensor_x, float sensor_y); -/* Camera Parameters: +/** + * Camera Parameters: * * Intermediate struct for storing camera parameters from various sources, - * to unify computation of viewplane, window matrix, ... */ - + * to unify computation of view-plane, window matrix, ... etc. + */ typedef struct CameraParams { /* lens */ bool is_ortho; @@ -84,7 +88,7 @@ typedef struct CameraParams { float winmat[4][4]; } CameraParams; -/* values for CameraParams.zoom, need to be taken into account for some operations */ +/* Values for CameraParams.zoom, need to be taken into account for some operations. */ #define CAMERA_PARAM_ZOOM_INIT_CAMOB 1.0f #define CAMERA_PARAM_ZOOM_INIT_PERSP 2.0f @@ -97,14 +101,17 @@ void BKE_camera_params_from_view3d(CameraParams *params, void BKE_camera_params_compute_viewplane( CameraParams *params, int winx, int winy, float aspx, float aspy); +/** + * View-plane is assumed to be already computed. + */ void BKE_camera_params_compute_matrix(CameraParams *params); /* Camera View Frame */ void BKE_camera_view_frame_ex(const struct Scene *scene, const struct Camera *camera, - const float drawsize, - const bool do_clip, + float drawsize, + bool do_clip, const float scale[3], float r_asp[2], float r_shift[2], @@ -114,6 +121,11 @@ void BKE_camera_view_frame(const struct Scene *scene, const struct Camera *camera, float r_vec[4][3]); +/** + * \param r_scale: only valid/useful for orthographic cameras. + * + * \note Don't move the camera, just yield the fit location. + */ bool BKE_camera_view_frame_fit_to_scene(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *camera_ob, @@ -128,12 +140,18 @@ bool BKE_camera_view_frame_fit_to_coords(const struct Depsgraph *depsgraph, /* Camera multi-view API */ +/** + * Returns the camera to be used for render. + */ struct Object *BKE_camera_multiview_render(const struct Scene *scene, struct Object *camera, const char *viewname); +/** + * The view matrix is used by the viewport drawing, it is basically the inverted model matrix. + */ void BKE_camera_multiview_view_matrix(const struct RenderData *rd, const struct Object *camera, - const bool is_left, + bool is_left, float r_viewmat[4][4]); void BKE_camera_multiview_model_matrix(const struct RenderData *rd, const struct Object *camera, @@ -158,6 +176,7 @@ bool BKE_camera_multiview_spherical_stereo(const struct RenderData *rd, const struct Object *camera); /* Camera background image API */ + struct CameraBGImage *BKE_camera_background_image_new(struct Camera *cam); void BKE_camera_background_image_remove(struct Camera *cam, struct CameraBGImage *bgpic); void BKE_camera_background_image_clear(struct Camera *cam); diff --git a/source/blender/blenkernel/BKE_cloth.h b/source/blender/blenkernel/BKE_cloth.h index dbf285feb92..ee73b926886 100644 --- a/source/blender/blenkernel/BKE_cloth.h +++ b/source/blender/blenkernel/BKE_cloth.h @@ -235,7 +235,9 @@ int cloth_bvh_collision(struct Depsgraph *depsgraph, /* cloth.c */ /* Needed for modifier.c */ +/** Frees all. */ void cloth_free_modifier_extern(struct ClothModifierData *clmd); +/** Frees all. */ void cloth_free_modifier(struct ClothModifierData *clmd); void clothModifier_do(struct ClothModifierData *clmd, struct Depsgraph *depsgraph, diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h index 2c7143be60e..402bffea91d 100644 --- a/source/blender/blenkernel/BKE_collection.h +++ b/source/blender/blenkernel/BKE_collection.h @@ -54,25 +54,56 @@ typedef struct CollectionParent { /* Collections */ +/** + * Add a collection to a collection ListBase and synchronize all render layers + * The ListBase is NULL when the collection is to be added to the master collection + */ struct Collection *BKE_collection_add(struct Main *bmain, struct Collection *parent, const char *name); +/** + * Add \a collection_dst to all scene collections that reference object \a ob_src is in. + * Used to replace an instance object with a collection (library override operator). + * + * Logic is very similar to #BKE_collection_object_add_from(). + */ void BKE_collection_add_from_object(struct Main *bmain, struct Scene *scene, const struct Object *ob_src, struct Collection *collection_dst); +/** + * Add \a collection_dst to all scene collections that reference collection \a collection_src is + * in. + * + * Logic is very similar to #BKE_collection_object_add_from(). + */ void BKE_collection_add_from_collection(struct Main *bmain, struct Scene *scene, struct Collection *collection_src, struct Collection *collection_dst); +/** + * Free (or release) any data used by this collection (does not free the collection itself). + */ void BKE_collection_free_data(struct Collection *collection); +/** + * Remove a collection, optionally removing its child objects or moving + * them to parent collections. + */ bool BKE_collection_delete(struct Main *bmain, struct Collection *collection, bool hierarchy); +/** + * Make a deep copy (aka duplicate) of the given collection and all of its children, recursively. + * + * \warning This functions will clear all \a bmain #ID.idnew pointers, unless \a + * #LIB_ID_DUPLICATE_IS_SUBPROCESS duplicate option is passed on, in which case caller is + * responsible to reconstruct collection dependencies information's + * (i.e. call #BKE_main_collection_sync). + */ struct Collection *BKE_collection_duplicate(struct Main *bmain, struct Collection *parent, struct Collection *collection, - const uint duplicate_flags, - const uint duplicate_options); + uint duplicate_flags, + uint duplicate_options); /* Master Collection for Scene */ @@ -91,28 +122,74 @@ struct Collection *BKE_collection_object_find(struct Main *bmain, struct Object *ob); bool BKE_collection_is_empty(const struct Collection *collection); +/** + * Add object to collection + */ bool BKE_collection_object_add(struct Main *bmain, struct Collection *collection, struct Object *ob); +/** + * Add \a ob_dst to all scene collections that reference object \a ob_src is in. + * Used for copying objects. + * + * Logic is very similar to #BKE_collection_add_from_object() + */ void BKE_collection_object_add_from(struct Main *bmain, struct Scene *scene, struct Object *ob_src, struct Object *ob_dst); +/** + * Remove object from collection. + */ bool BKE_collection_object_remove(struct Main *bmain, struct Collection *collection, struct Object *object, - const bool free_us); + bool free_us); +/** + * Move object from a collection into another + * + * If source collection is NULL move it from all the existing collections. + */ void BKE_collection_object_move(struct Main *bmain, struct Scene *scene, struct Collection *collection_dst, struct Collection *collection_src, struct Object *ob); +/** + * Remove object from all collections of scene + */ bool BKE_scene_collections_object_remove(struct Main *bmain, struct Scene *scene, struct Object *object, - const bool free_us); + bool free_us); + +/** + * Check all collections in \a bmain (including embedded ones in scenes) for CollectionObject with + * NULL object pointer, and remove them. + */ void BKE_collections_object_remove_nulls(struct Main *bmain); + +/** + * Check all collections in \a bmain (including embedded ones in scenes) for duplicate + * CollectionObject with a same object pointer within a same object, and remove them. + * + * NOTE: Always keeps the first of the detected duplicates. + */ +void BKE_collections_object_remove_duplicates(struct Main *bmain); + +/** + * Remove all NULL children from parent collections of changed \a collection. + * This is used for library remapping, where these pointers have been set to NULL. + * Otherwise this should never happen. + * + * \note caller must ensure #BKE_main_collection_sync_remap() is called afterwards! + * + * \param parent_collection: The collection owning the pointers that were remapped. May be \a NULL, + * in which case whole \a bmain database of collections is checked. + * \param child_collection: The collection that was remapped to another pointer. May be \a NULL, + * in which case whole \a bmain database of collections is checked. + */ void BKE_collections_child_remove_nulls(struct Main *bmain, struct Collection *parent_collection, struct Collection *child_collection); @@ -136,9 +213,24 @@ struct Base *BKE_collection_or_layer_objects(const struct ViewLayer *view_layer, /* Editing. */ -struct Collection *BKE_collection_from_index(struct Scene *scene, const int index); +/** + * Return Scene Collection for a given index. + * + * The index is calculated from top to bottom counting the children before the siblings. + */ +struct Collection *BKE_collection_from_index(struct Scene *scene, int index); +/** + * The automatic/fallback name of a new collection. + */ void BKE_collection_new_name_get(struct Collection *collection_parent, char *rname); +/** + * The name to show in the interface. + */ const char *BKE_collection_ui_name_get(struct Collection *collection); +/** + * Select all the objects in this Collection (and its nested collections) for this ViewLayer. + * Return true if any object was selected. + */ bool BKE_collection_objects_select(struct ViewLayer *view_layer, struct Collection *collection, bool deselect); @@ -162,13 +254,36 @@ bool BKE_collection_move(struct Main *bmain, bool relative_after, struct Collection *collection); +/** + * Find potential cycles in collections. + * + * \param new_ancestor: the potential new owner of given \a collection, + * or the collection to check if the later is NULL. + * \param collection: the collection we want to add to \a new_ancestor, + * may be NULL if we just want to ensure \a new_ancestor does not already have cycles. + * \return true if a cycle is found. + */ bool BKE_collection_cycle_find(struct Collection *new_ancestor, struct Collection *collection); +/** + * Find and fix potential cycles in collections. + * + * \param collection: The collection to check for existing cycles. + * \return true if cycles are found and fixed. + */ bool BKE_collection_cycles_fix(struct Main *bmain, struct Collection *collection); bool BKE_collection_has_collection(const struct Collection *parent, const struct Collection *collection); +/** + * Rebuild parent relationships from child ones, for all children of given \a collection. + * + * \note Given collection is assumed to already have valid parents. + */ void BKE_collection_parent_relations_rebuild(struct Collection *collection); +/** + * Rebuild parent relationships from child ones, for all collections in given \a bmain. + */ void BKE_main_collections_parent_relations_rebuild(struct Main *bmain); /* .blend file I/O */ @@ -224,6 +339,10 @@ typedef void (*BKE_scene_collections_Cb)(struct Collection *ob, void *data); /* Iteration over collections in scene. */ +/** + * Only use this in non-performance critical situations + * (it iterates over all scene collections twice) + */ void BKE_scene_collections_iterator_begin(struct BLI_Iterator *iter, void *data_in); void BKE_scene_collections_iterator_next(struct BLI_Iterator *iter); void BKE_scene_collections_iterator_end(struct BLI_Iterator *iter); @@ -232,6 +351,13 @@ void BKE_scene_objects_iterator_begin(struct BLI_Iterator *iter, void *data_in); void BKE_scene_objects_iterator_next(struct BLI_Iterator *iter); void BKE_scene_objects_iterator_end(struct 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.). + */ struct GSet *BKE_scene_objects_as_gset(struct Scene *scene, struct GSet *objects_gset); #define FOREACH_SCENE_COLLECTION_BEGIN(scene, _instance) \ diff --git a/source/blender/blenkernel/BKE_collision.h b/source/blender/blenkernel/BKE_collision.h index 2c21b7355d6..d4c5c03ea79 100644 --- a/source/blender/blenkernel/BKE_collision.h +++ b/source/blender/blenkernel/BKE_collision.h @@ -116,12 +116,15 @@ void bvhtree_update_from_mvert(struct BVHTree *bvhtree, ///////////////////////////////////////////////// -/* move Collision modifier object inter-frame with step = [0,1] - * defined in collisions.c */ +/** + * Move Collision modifier object inter-frame with step = [0,1] + * + * \param step: is limited from 0 (frame start position) to 1 (frame end position). + */ void collision_move_object(struct CollisionModifierData *collmd, - const float step, - const float prevstep, - const bool moving_bvh); + float step, + float prevstep, + bool moving_bvh); void collision_get_collider_velocity(float vel_old[3], float vel_new[3], @@ -135,6 +138,11 @@ typedef struct CollisionRelation { struct Object *ob; } CollisionRelation; +/** + * Create list of collision relations in the collection or entire scene. + * This is used by the depsgraph to build relations, as well as faster + * lookup of colliders during evaluation. + */ struct ListBase *BKE_collision_relations_create(struct Depsgraph *depsgraph, struct Collection *collection, unsigned int modifier_type); @@ -142,6 +150,10 @@ void BKE_collision_relations_free(struct ListBase *relations); /* Collision object lists for physics simulation evaluation. */ +/** + * Create effective list of colliders from relations built beforehand. + * Self will be excluded. + */ struct Object **BKE_collision_objects_create(struct Depsgraph *depsgraph, struct Object *self, struct Collection *collection, @@ -155,6 +167,10 @@ typedef struct ColliderCache { struct CollisionModifierData *collmd; } ColliderCache; +/** + * Create effective list of colliders from relations built beforehand. + * Self will be excluded. + */ struct ListBase *BKE_collider_cache_create(struct Depsgraph *depsgraph, struct Object *self, struct Collection *collection); diff --git a/source/blender/blenkernel/BKE_colorband.h b/source/blender/blenkernel/BKE_colorband.h index 0f46ced8b06..554b0f36b60 100644 --- a/source/blender/blenkernel/BKE_colorband.h +++ b/source/blender/blenkernel/BKE_colorband.h @@ -34,7 +34,7 @@ struct ColorBand; void BKE_colorband_init(struct ColorBand *coba, bool rangetype); void BKE_colorband_init_from_table_rgba(struct ColorBand *coba, const float (*array)[4], - const int array_len, + int array_len, bool filter_sample); struct ColorBand *BKE_colorband_add(bool rangetype); bool BKE_colorband_evaluate(const struct ColorBand *coba, float in, float out[4]); diff --git a/source/blender/blenkernel/BKE_colortools.h b/source/blender/blenkernel/BKE_colortools.h index ec2262d4f60..5ded49106da 100644 --- a/source/blender/blenkernel/BKE_colortools.h +++ b/source/blender/blenkernel/BKE_colortools.h @@ -59,52 +59,104 @@ enum { CURVEMAP_SLOPE_POS_NEG = 2, }; +/** + * Reset the view for current curve. + */ void BKE_curvemapping_reset_view(struct CurveMapping *cumap); void BKE_curvemap_reset(struct CurveMap *cuma, const struct rctf *clipr, int preset, int slope); -void BKE_curvemap_remove(struct CurveMap *cuma, const short flag); +/** + * Removes with flag set. + */ +void BKE_curvemap_remove(struct CurveMap *cuma, short flag); +/** + * Remove specified point. + */ bool BKE_curvemap_remove_point(struct CurveMap *cuma, struct CurveMapPoint *cmp); struct CurveMapPoint *BKE_curvemap_insert(struct CurveMap *cuma, float x, float y); +/** + * \param type: #eBezTriple_Handle + */ void BKE_curvemap_handle_set(struct CurveMap *cuma, int type); -void BKE_curvemapping_changed(struct CurveMapping *cumap, const bool rem_doubles); +/** + * \note only does current curvemap!. + */ +void BKE_curvemapping_changed(struct CurveMapping *cumap, bool rem_doubles); void BKE_curvemapping_changed_all(struct CurveMapping *cumap); -/* call before _all_ evaluation functions */ +/** + * Call before _all_ evaluation functions. + */ void BKE_curvemapping_init(struct CurveMapping *cumap); -/* keep these (const CurveMap) - to help with thread safety */ -/* single curve, no table check */ +/** + * Keep these `const CurveMap` - to help with thread safety. + * \note Single curve, no table check. + * \note Table should be verified. + */ float BKE_curvemap_evaluateF(const struct CurveMapping *cumap, const struct CurveMap *cuma, float value); -/* single curve, with table check */ +/** + * Single curve, with table check. + * Works with curve 'cur'. + */ float BKE_curvemapping_evaluateF(const struct CurveMapping *cumap, int cur, float value); +/** + * Vector case. + */ void BKE_curvemapping_evaluate3F(const struct CurveMapping *cumap, float vecout[3], const float vecin[3]); +/** + * RGB case, no black/white points, no pre-multiply. + */ void BKE_curvemapping_evaluateRGBF(const struct CurveMapping *cumap, float vecout[3], const float vecin[3]); +/** + * Byte version of #BKE_curvemapping_evaluateRGBF. + */ void BKE_curvemapping_evaluate_premulRGB(const struct CurveMapping *cumap, unsigned char vecout_byte[3], const unsigned char vecin_byte[3]); +/** + * Same as #BKE_curvemapping_evaluate_premulRGBF + * but black/bwmul are passed as args for the compositor + * where they can change per pixel. + * + * Use in conjunction with #BKE_curvemapping_set_black_white_ex + * + * \param black: Use instead of cumap->black + * \param bwmul: Use instead of cumap->bwmul + */ void BKE_curvemapping_evaluate_premulRGBF_ex(const struct CurveMapping *cumap, float vecout[3], const float vecin[3], const float black[3], const float bwmul[3]); +/** + * RGB with black/white points and pre-multiply. tables are checked. + */ 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 */ -void BKE_curvemapping_premultiply(struct CurveMapping *cumap, int restore); +/** + * Call when you do images etc, needs restore too. also verifies tables. + * non-const (these modify the curve). + */ +void BKE_curvemapping_premultiply(struct CurveMapping *cumap, bool restore); void BKE_curvemapping_blend_write(struct BlendWriter *writer, const struct CurveMapping *cumap); void BKE_curvemapping_curves_blend_write(struct BlendWriter *writer, const struct CurveMapping *cumap); +/** + * \note `cumap` itself has been read already. + */ void BKE_curvemapping_blend_read(struct BlendDataReader *reader, struct CurveMapping *cumap); void BKE_histogram_update_sample_line(struct Histogram *hist, @@ -122,16 +174,20 @@ void BKE_color_managed_display_settings_init(struct ColorManagedDisplaySettings void BKE_color_managed_display_settings_copy(struct ColorManagedDisplaySettings *new_settings, const struct ColorManagedDisplaySettings *settings); -/* Initialize view settings to be best suitable for render type of viewing. +/** + * Initialize view settings to be best suitable for render type of viewing. * This will use default view transform from the OCIO configuration if none - * is specified. */ + * is specified. + */ void BKE_color_managed_view_settings_init_render( struct ColorManagedViewSettings *settings, const struct ColorManagedDisplaySettings *display_settings, const char *view_transform); -/* Initialize view settings which are best suitable for viewing non-render - * images. For example,s movie clips while tracking. */ +/** + * Initialize view settings which are best suitable for viewing non-render images. + * For example,s movie clips while tracking. + */ void BKE_color_managed_view_settings_init_default( struct ColorManagedViewSettings *settings, const struct ColorManagedDisplaySettings *display_settings); diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h index 784b395dfa5..55e5cd0a149 100644 --- a/source/blender/blenkernel/BKE_constraint.h +++ b/source/blender/blenkernel/BKE_constraint.h @@ -45,7 +45,7 @@ extern "C" { typedef struct bConstraintOb { /** to get evaluated armature. */ struct Depsgraph *depsgraph; - /** for system time, part of deglobalization, code nicer later with local time (ton) */ + /** for system time, part of de-globalization, code nicer later with local time (ton) */ struct Scene *scene; /** if pchan, then armature that it comes from, otherwise constraint owner */ struct Object *ob; @@ -61,7 +61,7 @@ typedef struct bConstraintOb { /** type of owner. */ short type; - /** rotation order for constraint owner (as defined in eEulerRotationOrders in BLI_math.h) */ + /** rotation order for constraint owner (as defined in #eEulerRotationOrders in BLI_math.h) */ short rotOrder; } bConstraintOb; @@ -76,7 +76,7 @@ typedef void (*ConstraintIDFunc)(struct bConstraint *con, /* ....... */ /** - * Constraint Type-Info (shorthand in code = cti): + * Constraint Type-Info (shorthand in code = `cti`): * This struct provides function pointers for runtime, so that functions can be * written more generally (with fewer/no special exceptions for various constraints). * @@ -138,49 +138,105 @@ typedef struct bConstraintTypeInfo { } bConstraintTypeInfo; /* Function Prototypes for bConstraintTypeInfo's */ + +/** + * This function should always be used to get the appropriate type-info, as it + * has checks which prevent segfaults in some weird cases. + */ const bConstraintTypeInfo *BKE_constraint_typeinfo_get(struct bConstraint *con); +/** + * This function should be used for getting the appropriate type-info when only + * a constraint type is known. + */ const bConstraintTypeInfo *BKE_constraint_typeinfo_from_type(int type); /* ---------------------------------------------------------------------------- */ /* Constraint function prototypes */ + +/** + * Find the first available, non-duplicate name for a given constraint. + */ void BKE_constraint_unique_name(struct bConstraint *con, struct ListBase *list); -struct bConstraint *BKE_constraint_duplicate_ex(struct bConstraint *src, - const int flag, - const bool do_extern); +/** + * Allocate and duplicate a single constraint, outside of any object/pose context. + */ +struct bConstraint *BKE_constraint_duplicate_ex(struct bConstraint *src, int flag, bool do_extern); +/** + * Add a copy of the given constraint for the given bone. + */ struct bConstraint *BKE_constraint_copy_for_pose(struct Object *ob, struct bPoseChannel *pchan, struct bConstraint *src); +/** + * Add a copy of the given constraint for the given object. + */ struct bConstraint *BKE_constraint_copy_for_object(struct Object *ob, struct bConstraint *src); void BKE_constraints_free(struct ListBase *list); +/** + * Free all constraints from a constraint-stack. + */ void BKE_constraints_free_ex(struct ListBase *list, bool do_id_user); void BKE_constraints_copy(struct ListBase *dst, const struct ListBase *src, bool do_extern); +/** + * Duplicate all of the constraints in a constraint stack. + */ void BKE_constraints_copy_ex(struct ListBase *dst, const struct ListBase *src, - const int flag, + int flag, bool do_extern); +/** + * Run the given callback on all ID-blocks in list of constraints. + */ void BKE_constraints_id_loop(struct ListBase *list, ConstraintIDFunc func, void *userdata); void BKE_constraint_free_data(struct bConstraint *con); +/** + * Free data of a specific constraint if it has any info. + * Be sure to run #BIK_clear_data() when freeing an IK constraint, + * unless #DAG_relations_tag_update is called. + */ void BKE_constraint_free_data_ex(struct bConstraint *con, bool do_id_user); bool BKE_constraint_target_uses_bbone(struct bConstraint *con, struct bConstraintTarget *ct); /* Constraint API function prototypes */ + +/** + * Finds the 'active' constraint in a constraint stack. + */ struct bConstraint *BKE_constraints_active_get(struct ListBase *list); +/** + * Set the given constraint as the active one (clearing all the others). + */ void BKE_constraints_active_set(ListBase *list, struct bConstraint *con); struct bConstraint *BKE_constraints_find_name(struct ListBase *list, const char *name); +/** + * Finds the constraint that owns the given target within the object. + */ struct bConstraint *BKE_constraint_find_from_target(struct Object *ob, struct bConstraintTarget *tgt, struct bPoseChannel **r_pchan); +/** + * Check whether given constraint is not local (i.e. from linked data) when the object is a library + * override. + * + * \param con: May be NULL, in which case we consider it as a non-local constraint case. + */ bool BKE_constraint_is_nonlocal_in_liboverride(const struct Object *ob, const struct bConstraint *con); +/** + * Add new constraint for the given object. + */ struct bConstraint *BKE_constraint_add_for_object(struct Object *ob, const char *name, short type); +/** + * Add new constraint for the given bone. + */ struct bConstraint *BKE_constraint_add_for_pose(struct Object *ob, struct bPoseChannel *pchan, const char *name, @@ -190,8 +246,14 @@ bool BKE_constraint_remove_ex(ListBase *list, struct Object *ob, struct bConstraint *con, bool clear_dep); +/** + * Remove the specified constraint from the given constraint stack. + */ bool BKE_constraint_remove(ListBase *list, struct bConstraint *con); +/** + * Apply the specified constraint in the given constraint stack. + */ bool BKE_constraint_apply_for_object(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, @@ -217,25 +279,54 @@ bool BKE_constraint_apply_and_remove_for_pose(struct Depsgraph *depsgraph, void BKE_constraint_panel_expand(struct bConstraint *con); /* Constraints + Proxies function prototypes */ + +/** + * Rescue all constraints tagged as being #CONSTRAINT_PROXY_LOCAL + * (i.e. added to bone that's proxy-synced in this file). + */ void BKE_constraints_proxylocal_extract(struct ListBase *dst, struct ListBase *src); +/** + * Returns if the owner of the constraint is proxy-protected. + */ bool BKE_constraints_proxylocked_owner(struct Object *ob, struct bPoseChannel *pchan); /* Constraint Evaluation function prototypes */ + +/** + * This function MEM_calloc's a #bConstraintOb struct, + * that will need to be freed after evaluation. + */ struct bConstraintOb *BKE_constraints_make_evalob(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, void *subdata, short datatype); +/** + * Cleanup after constraint evaluation. + */ void BKE_constraints_clear_evalob(struct bConstraintOb *cob); +/** + * This function is responsible for the correct transformations/conversions + * of a matrix from one space to another for constraint evaluation. + * For now, this is only implemented for objects and pose-channels. + */ void BKE_constraint_mat_convertspace(struct Object *ob, struct bPoseChannel *pchan, struct bConstraintOb *cob, float mat[4][4], short from, short to, - const bool keep_scale); + bool keep_scale); +/** + * This function is a relic from the prior implementations of the constraints system, when all + * constraints either had one or no targets. It used to be called during the main constraint + * solving loop, but is now only used for the remaining cases for a few constraints. + * + * None of the actual calculations of the matrices should be done here! Also, this function is + * not to be used by any new constraints, particularly any that have multiple targets. + */ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph, struct Scene *scene, struct bConstraint *con, @@ -244,12 +335,22 @@ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph, void *ownerdata, float mat[4][4], float ctime); +/** + * Get the list of targets required for solving a constraint. + */ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph, struct bConstraint *con, struct bConstraintOb *ob, struct ListBase *targets, float ctime); void BKE_constraint_custom_object_space_get(float r_mat[4][4], struct bConstraint *con); +/** + * This function is called whenever constraints need to be evaluated. Currently, all + * constraints that can be evaluated are every time this gets run. + * + * #BKE_constraints_make_evalob and #BKE_constraints_clear_evalob should be called before and + * after running this function, to sort out cob. + */ void BKE_constraints_solve(struct Depsgraph *depsgraph, struct ListBase *conlist, struct bConstraintOb *cob, diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h index b0705ff411f..18c1848b737 100644 --- a/source/blender/blenkernel/BKE_context.h +++ b/source/blender/blenkernel/BKE_context.h @@ -247,10 +247,13 @@ PointerRNA CTX_data_pointer_get_type_silent(const bContext *C, const char *member, StructRNA *type); ListBase CTX_data_collection_get(const bContext *C, const char *member); -ListBase CTX_data_dir_get_ex(const bContext *C, - const bool use_store, - const bool use_rna, - const bool use_all); +/** + * \param C: Context. + * \param use_store: Use 'C->wm.store'. + * \param use_rna: Use Include the properties from 'RNA_Context'. + * \param use_all: Don't skip values (currently only "scene"). + */ +ListBase CTX_data_dir_get_ex(const bContext *C, bool use_store, bool use_rna, bool use_all); ListBase CTX_data_dir_get(const bContext *C); int /*eContextResult*/ CTX_data_get( const bContext *C, const char *member, PointerRNA *r_ptr, ListBase *r_lb, short *r_type); @@ -276,8 +279,9 @@ bool CTX_data_dir(const char *member); ListBase ctx_data_list; \ CollectionPointerLink *ctx_link; \ CTX_data_##member(C, &ctx_data_list); \ - for (ctx_link = ctx_data_list.first; ctx_link; ctx_link = ctx_link->next) { \ - Type instance = ctx_link->ptr.data; + for (ctx_link = (CollectionPointerLink *)ctx_data_list.first; ctx_link; \ + ctx_link = ctx_link->next) { \ + Type instance = (Type)ctx_link->ptr.data; #define CTX_DATA_END \ } \ @@ -297,6 +301,13 @@ int ctx_data_list_count(const bContext *C, int (*func)(const bContext *, ListBas struct Main *CTX_data_main(const bContext *C); struct Scene *CTX_data_scene(const bContext *C); +/** + * This is tricky. Sometimes the user overrides the render_layer + * but not the scene_collection. In this case what to do? + * + * If the scene_collection is linked to the #ViewLayer we use it. + * Otherwise we fallback to the active one of the #ViewLayer. + */ struct LayerCollection *CTX_data_layer_collection(const bContext *C); struct Collection *CTX_data_collection(const bContext *C); struct ViewLayer *CTX_data_view_layer(const bContext *C); @@ -306,7 +317,7 @@ struct ToolSettings *CTX_data_tool_settings(const bContext *C); const char *CTX_data_mode_string(const bContext *C); enum eContextObjectMode CTX_data_mode_enum_ex(const struct Object *obedit, const struct Object *ob, - const eObjectMode object_mode); + eObjectMode object_mode); enum eContextObjectMode CTX_data_mode_enum(const bContext *C); void CTX_data_main_set(bContext *C, struct Main *bmain); @@ -367,28 +378,34 @@ 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. +/** + * Gets pointer to the dependency graph. * If it doesn't exist yet, it will be allocated. * * The result dependency graph is NOT guaranteed to be up-to-date neither from relation nor from * evaluated data points of view. * - * NOTE: Can not be used if access to a fully evaluated datablock is needed. */ + * \note Can not be used if access to a fully evaluated data-block is needed. + */ struct Depsgraph *CTX_data_depsgraph_pointer(const bContext *C); -/* Get dependency graph which is expected to be fully evaluated. +/** + * Get dependency graph which is expected to be fully evaluated. * * In the release builds it is the same as CTX_data_depsgraph_pointer(). In the debug builds extra * sanity checks are done. Additionally, this provides more semantic meaning to what is exactly - * expected to happen. */ + * expected to happen. + */ struct Depsgraph *CTX_data_expect_evaluated_depsgraph(const bContext *C); -/* Gets fully updated and evaluated dependency graph. +/** + * Gets fully updated and evaluated dependency graph. * * All the relations and evaluated objects are guaranteed to be up to date. * - * NOTE: Will be expensive if there are relations or objects tagged for update. - * NOTE: If there are pending updates depsgraph hooks will be invoked. */ + * \note Will be expensive if there are relations or objects tagged for update. + * \note If there are pending updates depsgraph hooks will be invoked. + */ struct Depsgraph *CTX_data_ensure_evaluated_depsgraph(const bContext *C); /* Will Return NULL if depsgraph is not allocated yet. diff --git a/source/blender/blenkernel/BKE_crazyspace.h b/source/blender/blenkernel/BKE_crazyspace.h index b95be70379f..42d85c70bc1 100644 --- a/source/blender/blenkernel/BKE_crazyspace.h +++ b/source/blender/blenkernel/BKE_crazyspace.h @@ -26,24 +26,34 @@ #ifdef __cplusplus extern "C" { #endif + struct BMEditMesh; struct Depsgraph; struct Mesh; struct Object; +struct ReportList; struct Scene; /* crazyspace.c */ + +/** + * Disable subdivision-surface temporal, get mapped coordinates, and enable it. + */ float (*BKE_crazyspace_get_mapped_editverts(struct Depsgraph *depsgraph, struct Object *obedit))[3]; void BKE_crazyspace_set_quats_editmesh(struct BMEditMesh *em, float (*origcos)[3], float (*mappedcos)[3], float (*quats)[4], - const bool use_select); + bool use_select); void BKE_crazyspace_set_quats_mesh(struct Mesh *me, float (*origcos)[3], float (*mappedcos)[3], float (*quats)[4]); +/** + * Returns an array of deform matrices for crazy-space correction, + * and the number of modifiers left. + */ int BKE_crazyspace_get_first_deform_matrices_editbmesh(struct Depsgraph *depsgraph, struct Scene *, struct Object *, @@ -61,6 +71,31 @@ void BKE_crazyspace_build_sculpt(struct Depsgraph *depsgraph, float (**deformmats)[3][3], float (**deformcos)[3]); +/* -------------------------------------------------------------------- */ +/** \name Crazy-Space API + * \{ */ + +void BKE_crazyspace_api_eval(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *object, + struct ReportList *reports); + +void BKE_crazyspace_api_displacement_to_deformed(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement[3], + float r_displacement_deformed[3]); + +void BKE_crazyspace_api_displacement_to_original(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement_deformed[3], + float r_displacement[3]); + +void BKE_crazyspace_api_eval_clear(struct Object *object); + +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_cryptomatte.h b/source/blender/blenkernel/BKE_cryptomatte.h index e98a9b2b1fd..64fbb00b143 100644 --- a/source/blender/blenkernel/BKE_cryptomatte.h +++ b/source/blender/blenkernel/BKE_cryptomatte.h @@ -55,8 +55,11 @@ uint32_t BKE_cryptomatte_asset_hash(struct CryptomatteSession *session, const char *layer_name, const struct Object *object); float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash); +/** + * Find an ID in the given main that matches the given encoded float. + */ bool BKE_cryptomatte_find_name(const struct CryptomatteSession *session, - const float encoded_hash, + float encoded_hash, char *r_name, int name_len); diff --git a/source/blender/blenkernel/BKE_cryptomatte.hh b/source/blender/blenkernel/BKE_cryptomatte.hh index 9e205d01765..aa82166aa70 100644 --- a/source/blender/blenkernel/BKE_cryptomatte.hh +++ b/source/blender/blenkernel/BKE_cryptomatte.hh @@ -37,7 +37,8 @@ struct ID; namespace blender::bke::cryptomatte { -/* Format to a cryptomatte meta data key. +/** + * Format to a cryptomatte meta data key. * * Cryptomatte stores meta data. The keys are formatted containing a hash that * is generated from its layer name. @@ -48,7 +49,8 @@ namespace blender::bke::cryptomatte { std::string BKE_cryptomatte_meta_data_key(const StringRef layer_name, const StringRefNull key_name); -/* Extract the cryptomatte layer name from the given `render_pass_name`. +/** + * Extract the cryptomatte layer name from the given `render_pass_name`. * * Cryptomatte passes are formatted with a trailing number for storing multiple samples that belong * to the same cryptomatte layer. This function would remove the trailing numbers to determine the @@ -59,7 +61,7 @@ std::string BKE_cryptomatte_meta_data_key(const StringRef layer_name, * A render_pass_name could be 'View Layer.CryptoMaterial02'. The cryptomatte layer would be 'View * Layer.CryptoMaterial'. * - * NOTE: The return type is a sub-string of `render_pass_name` and therefore cannot outlive the + * \note The return type is a sub-string of `render_pass_name` and therefore cannot outlive the * `render_pass_name` internal data. */ StringRef BKE_cryptomatte_extract_layer_name(const StringRef render_pass_name); @@ -68,10 +70,22 @@ struct CryptomatteHash { uint32_t hash; CryptomatteHash(uint32_t hash); - CryptomatteHash(const char *name, const int name_len); + CryptomatteHash(const char *name, int name_len); static CryptomatteHash from_hex_encoded(blender::StringRef hex_encoded); std::string hex_encoded() const; + /** + Convert a cryptomatte hash to a float. + * + * Cryptomatte hashes are stored in float textures and images. The conversion is taken from the + * cryptomatte specification. See Floating point conversion section in + * https://github.com/Psyop/Cryptomatte/blob/master/specification/cryptomatte_specification.pdf. + * + * The conversion uses as many 32 bit floating point values as possible to minimize hash + * collisions. Unfortunately not all 32 bits can be used as NaN and Inf can be problematic. + * + * Note that this conversion assumes to be running on a L-endian system. + */ float float_encoded() const; }; diff --git a/source/blender/blenkernel/BKE_curve.h b/source/blender/blenkernel/BKE_curve.h index 5e474c0c5ac..25d49544330 100644 --- a/source/blender/blenkernel/BKE_curve.h +++ b/source/blender/blenkernel/BKE_curve.h @@ -75,8 +75,8 @@ typedef struct CVKeyIndex { #define SEGMENTSV(nu) (((nu)->flagv & CU_NURB_CYCLIC) ? (nu)->pntsv : (nu)->pntsv - 1) #define CU_DO_RADIUS(cu, nu) \ - ((((cu)->flag & (CU_PATH_RADIUS | CU_3D)) || (cu)->bevobj || (cu)->ext1 != 0.0f || \ - (cu)->ext2 != 0.0f) ? \ + ((((cu)->flag & (CU_PATH_RADIUS | CU_3D)) || (cu)->bevobj || (cu)->extrude != 0.0f || \ + (cu)->bevel_radius != 0.0f) ? \ 1 : \ 0) @@ -86,8 +86,11 @@ typedef struct CVKeyIndex { #define CU_DO_2DFILL(cu) (CU_IS_2D(cu) && (((cu)->flag & (CU_FRONT | CU_BACK)) != 0)) /* ** Curve ** */ +/** + * Frees edit-curve entirely. + */ void BKE_curve_editfont_free(struct Curve *cu); -void BKE_curve_init(struct Curve *cu, const short curve_type); +void BKE_curve_init(struct Curve *cu, short curve_type); struct Curve *BKE_curve_add(struct Main *bmain, const char *name, int type); short BKE_curve_type_get(const struct Curve *cu); void BKE_curve_type_test(struct Object *ob); @@ -98,35 +101,43 @@ 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); +/* Basic vertex data functions. */ + 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]); bool BKE_curve_center_bounds(struct Curve *cu, float cent[3]); -void BKE_curve_transform_ex(struct Curve *cu, - const float mat[4][4], - const bool do_keys, - const bool do_props, - const float unit_scale); -void BKE_curve_transform(struct Curve *cu, - const float mat[4][4], - const bool do_keys, - const bool do_props); -void BKE_curve_translate(struct Curve *cu, const float offset[3], const bool do_keys); +void BKE_curve_transform_ex( + struct Curve *cu, const float mat[4][4], bool do_keys, bool do_props, float unit_scale); +void BKE_curve_transform(struct Curve *cu, const float mat[4][4], bool do_keys, bool do_props); +void BKE_curve_translate(struct Curve *cu, const float offset[3], bool do_keys); void BKE_curve_material_index_remove(struct Curve *cu, int index); bool BKE_curve_material_index_used(const struct Curve *cu, int index); void BKE_curve_material_index_clear(struct Curve *cu); bool BKE_curve_material_index_validate(struct Curve *cu); void BKE_curve_material_remap(struct Curve *cu, const unsigned int *remap, unsigned int remap_len); -void BKE_curve_smooth_flag_set(struct Curve *cu, const bool use_smooth); +void BKE_curve_smooth_flag_set(struct Curve *cu, bool use_smooth); +/** + * \return edit-nurbs or normal nurbs list. + */ ListBase *BKE_curve_nurbs_get(struct Curve *cu); const ListBase *BKE_curve_nurbs_get_for_read(const struct Curve *cu); int BKE_curve_nurb_vert_index_get(const struct Nurb *nu, const void *vert); void BKE_curve_nurb_active_set(struct Curve *cu, const struct Nurb *nu); struct Nurb *BKE_curve_nurb_active_get(struct Curve *cu); +/** + * Get active vert for curve. + */ void *BKE_curve_vert_active_get(struct Curve *cu); +/** + * Set active nurb and active vert for curve. + */ void BKE_curve_nurb_vert_active_set(struct Curve *cu, const struct Nurb *nu, const void *vert); +/** + * Get points to the active nurb and active vert for curve. + */ bool BKE_curve_nurb_vert_active_get(struct Curve *cu, struct Nurb **r_nu, void **r_vert); void BKE_curve_nurb_vert_active_validate(struct Curve *cu); @@ -138,11 +149,11 @@ void BKE_curve_nurbs_vert_coords_get(const struct ListBase *lb, void BKE_curve_nurbs_vert_coords_apply_with_mat4(struct ListBase *lb, const float (*vert_coords)[3], const float mat[4][4], - const bool constrain_2d); + bool constrain_2d); void BKE_curve_nurbs_vert_coords_apply(struct ListBase *lb, const float (*vert_coords)[3], - const bool constrain_2d); + bool constrain_2d); float (*BKE_curve_nurbs_key_vert_coords_alloc(const struct ListBase *lb, float *key, @@ -152,6 +163,9 @@ void BKE_curve_nurbs_key_vert_tilts_apply(struct ListBase *lb, const float *key) void BKE_curve_editNurb_keyIndex_delCV(struct GHash *keyindex, const void *cv); void BKE_curve_editNurb_keyIndex_free(struct GHash **keyindex); void BKE_curve_editNurb_free(struct Curve *cu); +/** + * Get list of nurbs from edit-nurbs structure. + */ struct ListBase *BKE_curve_editNurbs_get(struct Curve *cu); const struct ListBase *BKE_curve_editNurbs_get_for_read(const struct Curve *cu); @@ -159,8 +173,14 @@ void BKE_curve_bevelList_free(struct ListBase *bev); void BKE_curve_bevelList_make(struct Object *ob, const struct ListBase *nurbs, bool for_render); ListBase BKE_curve_bevel_make(const struct Curve *curve); +/** + * Forward differencing method for bezier curve. + */ void BKE_curve_forward_diff_bezier( float q0, float q1, float q2, float q3, float *p, int it, int stride); +/** + * Forward differencing method for first derivative of cubic bezier curve. + */ void BKE_curve_forward_diff_tangent_bezier( float q0, float q1, float q2, float q3, float *p, int it, int stride); @@ -168,36 +188,65 @@ void BKE_curve_rect_from_textbox(const struct Curve *cu, const struct TextBox *tb, struct rctf *r_rect); +/** + * This function is almost the same as #BKE_fcurve_correct_bezpart, + * but doesn't allow as large a tangent. + */ void BKE_curve_correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2]); /* ** Nurbs ** */ -bool BKE_nurbList_index_get_co(struct ListBase *editnurb, const int index, float r_co[3]); +bool BKE_nurbList_index_get_co(struct ListBase *editnurb, int index, float r_co[3]); int BKE_nurbList_verts_count(const struct ListBase *nurb); int BKE_nurbList_verts_count_without_handles(const struct ListBase *nurb); void BKE_nurbList_free(struct ListBase *lb); void BKE_nurbList_duplicate(struct ListBase *lb1, const struct ListBase *lb2); -void BKE_nurbList_handles_set(struct ListBase *editnurb, const char code); -void BKE_nurbList_handles_recalculate(struct ListBase *editnurb, - const bool calc_length, - const uint8_t flag); +/** + * \param code: + * - 1 (#HD_AUTO): set auto-handle. + * - 2 (#HD_VECT): set vector-handle. + * - 3 (#HD_ALIGN) it toggle, vector-handles become #HD_FREE. + * + * - 5: Set align, like 3 but no toggle. + * - 6: Clear align (setting #HD_FREE), like 3 but no toggle. + */ +void BKE_nurbList_handles_set(struct ListBase *editnurb, char code); +void BKE_nurbList_handles_recalculate(struct ListBase *editnurb, bool calc_length, uint8_t flag); void BKE_nurbList_handles_autocalc(ListBase *editnurb, uint8_t flag); void BKE_nurbList_flag_set(ListBase *editnurb, uint8_t flag, bool set); +/** + * Set \a flag for every point that already has \a from_flag set. + */ bool BKE_nurbList_flag_set_from_flag(ListBase *editnurb, uint8_t from_flag, uint8_t flag); void BKE_nurb_free(struct Nurb *nu); struct Nurb *BKE_nurb_duplicate(const struct Nurb *nu); +/** + * Copy the nurb but allow for different number of points (to be copied after this). + */ struct Nurb *BKE_nurb_copy(struct Nurb *src, int pntsu, int pntsv); void BKE_nurb_project_2d(struct Nurb *nu); +/** + * if use_radius is truth, minmax will take points' radius into account, + * which will make bound-box closer to beveled curve. + */ void BKE_nurb_minmax(const struct Nurb *nu, bool use_radius, float min[3], float max[3]); float BKE_nurb_calc_length(const struct Nurb *nu, int resolution); +/** + * \param coord_array: has to be `(3 * 4 * resolu * resolv)` in size, and zero-ed. + */ void BKE_nurb_makeFaces( const struct Nurb *nu, float *coord_array, int rowstride, int resolu, int resolv); +/** + * \param coord_array: Has to be `(3 * 4 * pntsu * resolu)` in size and zero-ed + * \param tilt_array: set when non-NULL + * \param radius_array: set when non-NULL + */ void BKE_nurb_makeCurve(const struct Nurb *nu, float *coord_array, float *tilt_array, @@ -206,18 +255,27 @@ void BKE_nurb_makeCurve(const struct Nurb *nu, int resolu, int stride); -unsigned int BKE_curve_calc_coords_axis_len(const unsigned int bezt_array_len, - const unsigned int resolu, - const bool is_cyclic, - const bool use_cyclic_duplicate_endpoint); +/** + * Calculate the length for arrays filled in by #BKE_curve_calc_coords_axis. + */ +unsigned int BKE_curve_calc_coords_axis_len(unsigned int bezt_array_len, + unsigned int resolu, + bool is_cyclic, + bool use_cyclic_duplicate_endpoint); +/** + * Calculate an array for the entire curve (cyclic or non-cyclic). + * \note Call for each axis. + * + * \param use_cyclic_duplicate_endpoint: Duplicate values at the beginning & end of the array. + */ void BKE_curve_calc_coords_axis(const struct BezTriple *bezt_array, - const unsigned int bezt_array_len, - const unsigned int resolu, - const bool is_cyclic, - const bool use_cyclic_duplicate_endpoint, + unsigned int bezt_array_len, + unsigned int resolu, + bool is_cyclic, + bool use_cyclic_duplicate_endpoint, /* array params */ - const unsigned int axis, - const unsigned int stride, + unsigned int axis, + unsigned int stride, float *r_points); void BKE_nurb_knot_calc_u(struct Nurb *nu); @@ -232,11 +290,14 @@ bool BKE_nurb_order_clamp_u(struct Nurb *nu); bool BKE_nurb_order_clamp_v(struct Nurb *nu); void BKE_nurb_direction_switch(struct Nurb *nu); -bool BKE_nurb_type_convert(struct Nurb *nu, - const short type, - const bool use_handles, - const char **r_err_msg); +/** + * \note caller must ensure active vertex remains valid. + */ +bool BKE_nurb_type_convert(struct Nurb *nu, short type, bool use_handles, const char **r_err_msg); +/** + * Be sure to call #BKE_nurb_knot_calc_u / #BKE_nurb_knot_calc_v after this. + */ void BKE_nurb_points_add(struct Nurb *nu, int number); void BKE_nurb_bezierPoints_add(struct Nurb *nu, int number); @@ -254,17 +315,31 @@ void BKE_nurb_bezt_calc_plane(struct Nurb *nu, struct BezTriple *bezt, float r_p void BKE_nurb_bpoint_calc_normal(struct Nurb *nu, struct BPoint *bp, float r_normal[3]); void BKE_nurb_bpoint_calc_plane(struct Nurb *nu, struct BPoint *bp, float r_plane[3]); +/** + * Recalculate the handles of a nurb bezier-triple. Acts based on handle selection with `SELECT` + * flag. To use a different flag, use #BKE_nurb_handle_calc_ex(). + */ void BKE_nurb_handle_calc(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, - const bool is_fcurve, - const char smoothing); + bool is_fcurve, + char smoothing); +/** + * Variant of #BKE_nurb_handle_calc() that allows calculating based on a different select flag. + * + * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. + * Usually #SELECT, but may want to use a different one at times + * (if caller does not operate on selection). + */ void BKE_nurb_handle_calc_ex(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, - const eBezTriple_Flag__Alias handle_sel_flag, - const bool is_fcurve, - const char smoothing); + eBezTriple_Flag__Alias handle_sel_flag, + bool is_fcurve, + char smoothing); +/** + * Similar to #BKE_nurb_handle_calc but for curves and figures out the previous and next for us. + */ void BKE_nurb_handle_calc_simple(struct Nurb *nu, struct BezTriple *bezt); void BKE_nurb_handle_calc_simple_auto(struct Nurb *nu, struct BezTriple *bezt); @@ -272,11 +347,23 @@ void BKE_nurb_handle_smooth_fcurve(struct BezTriple *bezt, int total, bool cycli void BKE_nurb_handles_calc(struct Nurb *nu); void BKE_nurb_handles_autocalc(struct Nurb *nu, uint8_t flag); +/** + * Update selected handle types to ensure valid state, e.g. deduce "Auto" types to concrete ones. + * Thereby \a sel_flag defines what qualifies as selected. + * Use when something has changed handle positions. + * + * The caller needs to recalculate handles. + * + * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`, + * but may want to use a different one at times (if caller does not operate on * selection). + * \param use_handle: Check selection state of individual handles, otherwise always update both + * handles if the key is selected. + */ void BKE_nurb_bezt_handle_test(struct BezTriple *bezt, - const eBezTriple_Flag__Alias sel_flag, - const bool use_handle, - const bool use_around_local); -void BKE_nurb_handles_test(struct Nurb *nu, const bool use_handles, const bool use_around_local); + eBezTriple_Flag__Alias sel_flag, + bool use_handle, + bool use_around_local); +void BKE_nurb_handles_test(struct Nurb *nu, bool use_handles, bool use_around_local); /* **** Depsgraph evaluation **** */ @@ -300,18 +387,18 @@ extern void (*BKE_curve_batch_cache_free_cb)(struct Curve *cu); * \{ */ unsigned int BKE_curve_decimate_bezt_array(struct BezTriple *bezt_array, - const unsigned int bezt_array_len, - const unsigned int resolu, - const bool is_cyclic, - const char flag_test, - const char flag_set, - const float error_sq_max, - const unsigned int error_target_len); + unsigned int bezt_array_len, + unsigned int resolu, + bool is_cyclic, + char flag_test, + char flag_set, + float error_sq_max, + unsigned int error_target_len); void BKE_curve_decimate_nurb(struct Nurb *nu, - const unsigned int resolu, - const float error_sq_max, - const unsigned int error_target_len); + unsigned int resolu, + float error_sq_max, + unsigned int error_target_len); /** \} */ @@ -322,26 +409,32 @@ void BKE_curve_decimate_nurb(struct Nurb *nu, void BKE_curve_deform_coords(const struct Object *ob_curve, const struct Object *ob_target, float (*vert_coords)[3], - const int vert_coords_len, + int vert_coords_len, const struct MDeformVert *dvert, - const int defgrp_index, - const short flag, - const short defaxis); + int defgrp_index, + short flag, + short defaxis); void BKE_curve_deform_coords_with_editmesh(const struct Object *ob_curve, const struct Object *ob_target, float (*vert_coords)[3], - const int vert_coords_len, - const int defgrp_index, - const short flag, - const short defaxis, + int vert_coords_len, + int defgrp_index, + short flag, + short defaxis, struct BMEditMesh *em_target); +/** + * \param orco: Input vec and orco = local coord in curve space + * orco is original not-animated or deformed reference point. + * + * The result written to `vec` and `r_mat`. + */ void BKE_curve_deform_co(const struct Object *ob_curve, const struct Object *ob_target, const float orco[3], float vec[3], - const int no_rot_axis, + int no_rot_axis, float r_mat[3][3]); /** \} */ diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh index cc1ef08908d..10649e8703f 100644 --- a/source/blender/blenkernel/BKE_curve_to_mesh.hh +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -16,16 +16,30 @@ #pragma once -struct Mesh; struct CurveEval; +struct Mesh; /** \file - * \ingroup geo + * \ingroup bke */ namespace blender::bke { -Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile); +/** + * 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, bool fill_caps); +/** + * 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); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curveprofile.h b/source/blender/blenkernel/BKE_curveprofile.h index 501ae70ecdb..aa79f29760d 100644 --- a/source/blender/blenkernel/BKE_curveprofile.h +++ b/source/blender/blenkernel/BKE_curveprofile.h @@ -34,8 +34,15 @@ struct BlendWriter; struct CurveProfile; struct CurveProfilePoint; +/** + * Sets the default settings and clip range for the profile widget. + * Does not generate either table. + */ void BKE_curveprofile_set_defaults(struct CurveProfile *profile); +/** + * Returns a pointer to a newly allocated curve profile, using the given preset. + */ struct CurveProfile *BKE_curveprofile_add(eCurveProfilePresets preset); void BKE_curveprofile_free_data(struct CurveProfile *profile); @@ -46,35 +53,90 @@ void BKE_curveprofile_copy_data(struct CurveProfile *target, const struct CurveP struct CurveProfile *BKE_curveprofile_copy(const struct CurveProfile *profile); +/** + * Move a point's handle, accounting for the alignment of handles with the #HD_ALIGN type. + * + * \param handle_1: Whether to move the 1st or 2nd control point. + * \param delta: The *relative* change in the handle's position. + * \note Requires #BKE_curveprofile_update call after. + * \return Whether the handle moved from its start position. + */ bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point, - const bool handle_1, - const bool snap, + bool handle_1, + bool snap, const float delta[2]); +/** + * Moves a control point, accounting for clipping and snapping, and moving free handles. + * + * \param snap: Whether to snap the point to the grid + * \param delta: The *relative* change of the point's location. + * \return Whether the point moved from its start position. + * \note Requires #BKE_curveprofile_update call after. + */ bool BKE_curveprofile_move_point(struct CurveProfile *profile, struct CurveProfilePoint *point, - const bool snap, + bool snap, const float delta[2]); +/** + * Removes a specific point from the path of control points. + * \note Requires #BKE_curveprofile_update call after. + */ bool BKE_curveprofile_remove_point(struct CurveProfile *profile, struct CurveProfilePoint *point); -void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, const short flag); +/** + * Removes every point in the widget with the supplied flag set, except for the first and last. + * + * \param flag: #CurveProfilePoint.flag. + * + * \note Requires #BKE_curveprofile_update call after. + */ +void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, short flag); +/** + * Adds a new point at the specified location. The choice for which points to place the new vertex + * between is made by checking which control point line segment is closest to the new point and + * placing the new vertex in between that segment's points. + * + * \note Requires #BKE_curveprofile_update call after. + */ struct CurveProfilePoint *BKE_curveprofile_insert(struct CurveProfile *profile, float x, float y); +/** + * Sets the handle type of the selected control points. + * \param type_1, type_2: Handle type for the first handle. HD_VECT, HD_AUTO, HD_FREE, or HD_ALIGN. + * \note Requires #BKE_curveprofile_update call after. + */ void BKE_curveprofile_selected_handle_set(struct CurveProfile *profile, int type_1, int type_2); +/** + * Flips the profile across the diagonal so that its orientation is reversed. + * + * \note Requires #BKE_curveprofile_update call after. + */ void BKE_curveprofile_reverse(struct CurveProfile *profile); +/** + * Reset the view to the clipping rectangle. + */ void BKE_curveprofile_reset_view(struct CurveProfile *profile); +/** + * Resets the profile to the current preset. + * + * \note Requires #BKE_curveprofile_update call after. + */ 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); +/** + * 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 + * of the sampled points. + */ void BKE_curveprofile_init(struct CurveProfile *profile, short segments_len); /* Called for a complete update of the widget after modifications */ @@ -83,22 +145,31 @@ enum { PROF_UPDATE_REMOVE_DOUBLES = (1 << 0), PROF_UPDATE_CLIP = (1 << 1), }; -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); +/** + * 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: Bit-field with fields defined in header file. + * Controls removing doubles and clipping. + */ +void BKE_curveprofile_update(struct CurveProfile *profile, int update_flags); -/* Length portion is the fraction of the total path length where we want the location */ +/** + * Does a single evaluation along the profile's path. + * Travels down (length_portion * path) length and returns the position at that point. + * Where length portion is the fraction of the total path length where we want the location. + * + * \param length_portion: The portion (0 to 1) of the path's full length to sample at. + * \note Requires #BKE_curveprofile_init or #BKE_curveprofile_update call before to fill table. + */ void BKE_curveprofile_evaluate_length_portion(const struct CurveProfile *profile, float length_portion, float *x_out, float *y_out); void BKE_curveprofile_blend_write(struct BlendWriter *writer, const struct CurveProfile *profile); +/** + * Expects that the curve profile itself has been read already. + */ void BKE_curveprofile_blend_read(struct BlendDataReader *reader, struct CurveProfile *profile); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index 0732f1e5190..00eae2e8e6e 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -84,10 +84,16 @@ void customData_mask_layers__print(const struct CustomData_MeshMasks *mask); typedef void (*cd_interp)( const void **sources, const float *weights, const float *sub_weights, int count, void *dest); typedef void (*cd_copy)(const void *source, void *dest, int count); -typedef bool (*cd_validate)(void *item, const uint totitems, const bool do_fixes); +typedef bool (*cd_validate)(void *item, uint totitems, bool do_fixes); +/** + * Update mask_dst with layers defined in mask_src (equivalent to a bit-wise OR). + */ void CustomData_MeshMasks_update(CustomData_MeshMasks *mask_dst, const CustomData_MeshMasks *mask_src); +/** + * Return True if all layers set in \a mask_required are also set in \a mask_ref + */ bool CustomData_MeshMasks_are_matching(const CustomData_MeshMasks *mask_ref, const CustomData_MeshMasks *mask_required); @@ -99,39 +105,50 @@ bool CustomData_layer_has_math(const struct CustomData *data, int layer_n); bool CustomData_layer_has_interp(const struct CustomData *data, int layer_n); /** - * Checks if any of the customdata layers has math. + * Checks if any of the custom-data layers has math. */ bool CustomData_has_math(const struct CustomData *data); bool CustomData_has_interp(const struct CustomData *data); +/** + * A non bmesh version would have to check `layer->data`. + */ bool CustomData_bmesh_has_free(const struct CustomData *data); /** - * Checks if any of the customdata layers is referenced. + * Checks if any of the custom-data layers is referenced. */ bool CustomData_has_referenced(const struct CustomData *data); -/* Copies the "value" (e.g. mloopuv uv or mloopcol colors) from one block to +/** + * Copies the "value" (e.g. mloopuv uv or mloopcol colors) from one block to * another, while not overwriting anything else (e.g. flags). probably only - * implemented for mloopuv/mloopcol, for now. */ + * implemented for mloopuv/mloopcol, for now. + */ void CustomData_data_copy_value(int type, const void *source, void *dest); -/* Same as above, but doing advanced mixing. - * Only available for a few types of data (like colors...). */ +/** + * Mixes the "value" (e.g. mloopuv uv or mloopcol colors) from one block into + * another, while not overwriting anything else (e.g. flags). + */ void CustomData_data_mix_value( - int type, const void *source, void *dest, const int mixmode, const float mixfactor); + int type, const void *source, void *dest, int mixmode, float mixfactor); -/* compares if data1 is equal to data2. type is a valid CustomData type - * enum (e.g. CD_MLOOPUV). the layer type's equal function is used to compare - * the data, if it exists, otherwise memcmp is used. */ +/** + * Compares if data1 is equal to data2. type is a valid CustomData type + * enum (e.g. #CD_MLOOPUV). the layer type's equal function is used to compare + * the data, if it exists, otherwise #memcmp is used. + */ bool CustomData_data_equals(int type, const void *data1, const void *data2); void CustomData_data_initminmax(int type, void *min, void *max); void CustomData_data_dominmax(int type, const void *data, void *min, void *max); void CustomData_data_multiply(int type, void *data, float fac); void CustomData_data_add(int type, void *data1, const void *data2); -/* initializes a CustomData object with the same layer setup as source. - * mask is a bitfield where (mask & (1 << (layer type))) indicates - * if a layer should be copied or not. alloctype must be one of the above. */ +/** + * Initializes a CustomData object with the same layer setup as source. + * mask is a bitfield where `(mask & (1 << (layer type)))` indicates + * if a layer should be copied or not. alloctype must be one of the above. + */ void CustomData_copy(const struct CustomData *source, struct CustomData *dest, CustomDataMask mask, @@ -141,33 +158,40 @@ void CustomData_copy(const struct CustomData *source, /* BMESH_TODO, not really a public function but readfile.c needs it */ void CustomData_update_typemap(struct CustomData *data); -/* same as the above, except that this will preserve existing layers, and only - * add the layers that were not there yet */ +/** + * Same as the above, except that this will preserve existing layers, and only + * add the layers that were not there yet. + */ bool CustomData_merge(const struct CustomData *source, struct CustomData *dest, CustomDataMask mask, eCDAllocType alloctype, int totelem); -/* Reallocate custom data to a new element count. +/** + * Reallocate custom data to a new element count. * Only affects on data layers which are owned by the CustomData itself, * referenced data is kept unchanged, * - * NOTE: Take care of referenced layers by yourself! + * \note Take care of referenced layers by yourself! */ void CustomData_realloc(struct CustomData *data, int totelem); -/* bmesh version of CustomData_merge; merges the layouts of source and dest, - * then goes through the mesh and makes sure all the customdata blocks are - * consistent with the new layout. */ +/** + * BMesh version of CustomData_merge; merges the layouts of source and `dest`, + * then goes through the mesh and makes sure all the custom-data blocks are + * consistent with the new layout. + */ bool CustomData_bmesh_merge(const struct CustomData *source, struct CustomData *dest, CustomDataMask mask, eCDAllocType alloctype, struct BMesh *bm, - const char htype); + char htype); -/** NULL's all members and resets the typemap. */ +/** + * NULL's all members and resets the #CustomData.typemap. + */ void CustomData_reset(struct CustomData *data); /** @@ -175,19 +199,26 @@ void CustomData_reset(struct CustomData *data); */ void CustomData_free(struct CustomData *data, int totelem); -/* same as above, but only frees layers which matches the given mask. */ +/** + * Same as above, but only frees layers which matches the given mask. + */ void CustomData_free_typemask(struct CustomData *data, int totelem, CustomDataMask mask); -/* frees all layers with CD_FLAG_TEMPORARY */ +/** + * Frees all layers with #CD_FLAG_TEMPORARY. + */ void CustomData_free_temporary(struct CustomData *data, int totelem); -/* adds a data layer of the given type to the CustomData object, optionally +/** + * Adds a data layer of the given type to the #CustomData object, optionally * backed by an external data array. the different allocation types are * defined above. returns the data of the layer. */ void *CustomData_add_layer( struct CustomData *data, int type, eCDAllocType alloctype, void *layer, int totelem); -/* Same as above but accepts a name. */ +/** + * Same as above but accepts a name. + */ void *CustomData_add_layer_named(struct CustomData *data, int type, eCDAllocType alloctype, @@ -201,63 +232,70 @@ void *CustomData_add_layer_anonymous(struct CustomData *data, int totelem, const struct AnonymousAttributeID *anonymous_id); -/* frees the active or first data layer with the give type. +/** + * 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 * - * in editmode, use EDBM_data_layer_free instead of this function + * In edit-mode, use #EDBM_data_layer_free instead of this function. */ bool CustomData_free_layer(struct CustomData *data, int type, int totelem, int index); -/* frees the layer index with the give type. - * returns 1 on success, 0 if no layer with the given type is found +/** + * Frees the layer index with the give type. + * returns 1 on success, 0 if no layer with the given type is found. * - * in editmode, use EDBM_data_layer_free instead of this function + * In edit-mode, use #EDBM_data_layer_free instead of this function. */ bool CustomData_free_layer_active(struct CustomData *data, int type, int totelem); -/* same as above, but free all layers with type */ +/** + * Same as above, but free all layers with type. + */ void CustomData_free_layers(struct CustomData *data, int type, int totelem); -/* returns 1 if a layer with the specified type exists */ +/** + * Returns true if a layer with the specified type exists. + */ bool CustomData_has_layer(const struct CustomData *data, int type); -/* returns the number of layers with this type */ +/** + * Returns the number of layers with this type. + */ int CustomData_number_of_layers(const struct CustomData *data, int type); int CustomData_number_of_layers_typemask(const struct CustomData *data, CustomDataMask mask); -/* duplicate data of a layer with flag NOFREE, and remove that flag. - * returns the layer data */ -void *CustomData_duplicate_referenced_layer(struct CustomData *data, - const int type, - const int totelem); +/** + * Duplicate data of a layer with flag NOFREE, and remove that flag. + * \return the layer data. + */ +void *CustomData_duplicate_referenced_layer(struct CustomData *data, int type, int totelem); void *CustomData_duplicate_referenced_layer_n(struct CustomData *data, - const int type, - const int n, - const int totelem); + int type, + int n, + int totelem); void *CustomData_duplicate_referenced_layer_named(struct CustomData *data, - const int type, + int type, const char *name, - const int totelem); + int totelem); void *CustomData_duplicate_referenced_layer_anonymous( - CustomData *data, - const int type, - const struct AnonymousAttributeID *anonymous_id, - const int totelem); + CustomData *data, int type, const struct AnonymousAttributeID *anonymous_id, 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. */ +/** + * Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. + */ void CustomData_duplicate_referenced_layers(CustomData *data, int totelem); -/* set the CD_FLAG_NOCOPY flag in custom data layers where the mask is - * zero for the layer type, so only layer types specified by the mask - * will be copied +/** + * Set the #CD_FLAG_NOCOPY flag in custom data layers where the mask is + * zero for the layer type, so only layer types specified by the mask will be copied */ void CustomData_set_only_copy(const struct CustomData *data, CustomDataMask mask); -/* copies data from one CustomData object to another +/** + * Copies data from one CustomData object to another * objects need not be compatible, each source layer is copied to the - * first dest layer of correct type (if there is none, the layer is skipped) - * return 1 on success, 0 on failure + * first dest layer of correct type (if there is none, the layer is skipped). */ void CustomData_copy_data(const struct CustomData *source, struct CustomData *dest, @@ -285,9 +323,11 @@ void CustomData_bmesh_copy_data_exclude_by_type(const struct CustomData *source, struct CustomData *dest, void *src_block, void **dest_block, - const CustomDataMask mask_exclude); + CustomDataMask mask_exclude); -/* Copies data of a single layer of a given type. */ +/** + * Copies data of a single layer of a given type. + */ void CustomData_copy_layer_type_data(const struct CustomData *source, struct CustomData *destination, int type, @@ -295,22 +335,22 @@ void CustomData_copy_layer_type_data(const struct CustomData *source, int destination_index, int count); -/* frees data in a CustomData object - * return 1 on success, 0 on failure +/** + * Frees data in a #CustomData object. */ void CustomData_free_elem(struct CustomData *data, int index, int count); -/* interpolates data from one CustomData object to another - * objects need not be compatible, each source layer is interpolated to the - * first dest layer of correct type (if there is none, the layer is skipped) - * if weights == NULL or sub_weights == NULL, they default to all 1's +/** + * Interpolate given custom data source items into a single destination one. * - * src_indices gives the source elements to interpolate from - * weights gives the weight for each source element - * sub_weights is an array of matrices of weights for sub-elements (matrices - * should be source->subElems * source->subElems in size) - * count gives the number of source elements to interpolate from - * dest_index gives the dest element to write the interpolated value to + * \param src_indices: Indices of every source items to interpolate into the destination one. + * \param weights: The weight to apply to each source value individually. If NULL, they will be + * averaged. + * \param sub_weights: The weights of sub-items, only used to affect each corners of a + * tessellated face data (should always be and array of four values). + * \param count: The number of source items to interpolate. + * \param dest_index: Index of the destination item, in which to put the result of the + * interpolation. */ void CustomData_interp(const struct CustomData *source, struct CustomData *dest, @@ -319,6 +359,10 @@ void CustomData_interp(const struct CustomData *source, const float *sub_weights, int count, int dest_index); +/** + * \note src_blocks_ofs & dst_block_ofs + * must be pointers to the data, offset by layer->offset already. + */ void CustomData_bmesh_interp_n(struct CustomData *data, const void **src_blocks, const float *weights, @@ -333,30 +377,45 @@ void CustomData_bmesh_interp(struct CustomData *data, int count, void *dst_block); -/* swaps the data in the element corners, to new corners with indices as - * specified in corner_indices. for edges this is an array of length 2, for - * faces an array of length 4 */ +/** + * Swap data inside each item, for all layers. + * This only applies to item types that may store several sub-item data + * (e.g. corner data [UVs, VCol, ...] of tessellated faces). + * + * \param corner_indices: A mapping 'new_index -> old_index' of sub-item data. + */ void CustomData_swap_corners(struct CustomData *data, int index, const int *corner_indices); -void CustomData_swap(struct CustomData *data, const int index_a, const int index_b); +/** + * Swap two items of given custom data, in all available layers. + */ +void CustomData_swap(struct CustomData *data, int index_a, int index_b); -/* gets a pointer to the data element at index from the first layer of type - * returns NULL if there is no layer of type +/** + * Gets a pointer to the data element at index from the first layer of type. + * \return NULL if there is no layer of type. */ void *CustomData_get(const struct CustomData *data, int index, int type); void *CustomData_get_n(const struct CustomData *data, int type, int index, int n); + +/* BMesh Custom Data Functions. + * Should replace edit-mesh ones with these as well, due to more efficient memory alloc. */ + void *CustomData_bmesh_get(const struct CustomData *data, void *block, int type); void *CustomData_bmesh_get_n(const struct CustomData *data, void *block, int type, int n); -/* gets the layer at physical index n, with no type checking. +/** + * Gets from the layer at physical index `n`, + * \note doesn't check type. */ void *CustomData_bmesh_get_layer_n(const struct CustomData *data, void *block, int n); bool CustomData_set_layer_name(const struct CustomData *data, int type, int n, const char *name); const char *CustomData_get_layer_name(const struct CustomData *data, int type, int n); -/* gets a pointer to the active or first layer of type - * returns NULL if there is no layer of type +/** + * Gets a pointer to the active or first layer of type. + * \return NULL if there is no layer of type. */ void *CustomData_get_layer(const struct CustomData *data, int type); void *CustomData_get_layer_n(const struct CustomData *data, int type, int n); @@ -377,9 +436,9 @@ int CustomData_get_render_layer(const struct CustomData *data, int type); int CustomData_get_clone_layer(const struct CustomData *data, int type); int CustomData_get_stencil_layer(const struct CustomData *data, int type); -/* copies the data from source to the data element at index in the first - * layer of type - * no effect if there is no layer of type +/** + * Copies the data from source to the data element at index in the first layer of type + * no effect if there is no layer of type. */ void CustomData_set(const struct CustomData *data, int index, int type, const void *source); @@ -390,42 +449,63 @@ void CustomData_bmesh_set(const struct CustomData *data, void CustomData_bmesh_set_n( struct CustomData *data, void *block, int type, int n, const void *source); -/* sets the data of the block at physical layer n. no real type checking - * is performed. +/** + * Sets the data of the block at physical layer n. + * no real type checking is performed. */ void CustomData_bmesh_set_layer_n(struct CustomData *data, void *block, int n, const void *source); -/* set the pointer of to the first layer of type. the old data is not freed. - * returns the value of ptr if the layer is found, NULL otherwise +/** + * Set the pointer of to the first layer of type. the old data is not freed. + * returns the value of `ptr` if the layer is found, NULL otherwise. */ void *CustomData_set_layer(const struct CustomData *data, int type, void *ptr); void *CustomData_set_layer_n(const struct CustomData *data, int type, int n, void *ptr); -/* sets the nth layer of type as active */ +/** + * Sets the nth layer of type as active. + */ void CustomData_set_layer_active(struct CustomData *data, int type, int n); void CustomData_set_layer_render(struct CustomData *data, int type, int n); void CustomData_set_layer_clone(struct CustomData *data, int type, int n); void CustomData_set_layer_stencil(struct CustomData *data, int type, int n); -/* same as above but works with an index from CustomData_get_layer_index */ +/** + * For using with an index from #CustomData_get_active_layer_index and + * #CustomData_get_render_layer_index. + */ void CustomData_set_layer_active_index(struct CustomData *data, int type, int n); void CustomData_set_layer_render_index(struct CustomData *data, int type, int n); void CustomData_set_layer_clone_index(struct CustomData *data, int type, int n); void CustomData_set_layer_stencil_index(struct CustomData *data, int type, int n); -/* adds flag to the layer flags */ +/** + * Adds flag to the layer flags. + */ void CustomData_set_layer_flag(struct CustomData *data, int type, int flag); void CustomData_clear_layer_flag(struct CustomData *data, int type, int flag); void CustomData_bmesh_set_default(struct CustomData *data, void **block); void CustomData_bmesh_free_block(struct CustomData *data, void **block); +/** + * Same as #CustomData_bmesh_free_block but zero the memory rather than freeing. + */ void CustomData_bmesh_free_block_data(struct CustomData *data, void *block); +/** + * A selective version of #CustomData_bmesh_free_block_data. + */ void CustomData_bmesh_free_block_data_exclude_by_type(struct CustomData *data, void *block, - const CustomDataMask mask_exclude); + CustomDataMask mask_exclude); -/* copy custom data to/from layers as in mesh/derivedmesh, to editmesh - * blocks of data. the CustomData's must not be compatible */ +/** + * Copy custom data to/from layers as in mesh/derived-mesh, to edit-mesh + * blocks of data. the CustomData's must not be compatible. + * + * \param use_default_init: initializes data which can't be copied, + * typically you'll want to use this if the BM_xxx create function + * is called with BM_CREATE_SKIP_CD flag + */ void CustomData_to_bmesh_block(const struct CustomData *source, struct CustomData *dest, int src_index, @@ -436,17 +516,34 @@ void CustomData_from_bmesh_block(const struct CustomData *source, void *src_block, int dest_index); -/* query info over types */ +/** + * Query info over types. + */ void CustomData_file_write_info(int type, const char **r_struct_name, int *r_struct_num); int CustomData_sizeof(int type); -/* get the name of a layer type */ +/** + * Get the name of a layer type. + */ const char *CustomData_layertype_name(int type); +/** + * Can only ever be one of these. + */ bool CustomData_layertype_is_singleton(int type); +/** + * Has dynamically allocated members. + * This is useful to know if operations such as #memcmp are + * valid when comparing data from two layers. + */ bool CustomData_layertype_is_dynamic(int type); -int CustomData_layertype_layers_max(const int type); +/** + * \return Maximum number of layers of given \a type, -1 means 'no limit'. + */ +int CustomData_layertype_layers_max(int type); -/* make sure the name of layer at index is unique */ +/** + * Make sure the name of layer at index is unique. + */ void CustomData_set_layer_unique_name(struct CustomData *data, int index); void CustomData_validate_layer_name(const struct CustomData *data, @@ -454,26 +551,46 @@ void CustomData_validate_layer_name(const struct CustomData *data, const char *name, char *outname); -/* for file reading compatibility, returns false if the layer was freed, - * only after this test passes, layer->data should be assigned */ +/** + * For file reading compatibility, returns false if the layer was freed, + * only after this test passes, `layer->data` should be assigned. + */ bool CustomData_verify_versions(struct CustomData *data, int index); -/* BMesh specific custom-data stuff. */ +/* BMesh specific custom-data stuff. + * + * Needed to convert to/from different face representation (for versioning). */ + void CustomData_to_bmeshpoly(struct CustomData *fdata, struct CustomData *ldata, int totloop); void CustomData_from_bmeshpoly(struct CustomData *fdata, struct CustomData *ldata, int total); void CustomData_bmesh_update_active_layers(struct CustomData *fdata, struct CustomData *ldata); +/** + * Update active indices for active/render/clone/stencil custom data layers + * based on indices from fdata layers + * used by do_versions in `readfile.c` when creating pdata and ldata for pre-bmesh + * meshes and needed to preserve active/render/clone/stencil flags set in pre-bmesh files. + */ void CustomData_bmesh_do_versions_update_active_layers(struct CustomData *fdata, struct CustomData *ldata); -void CustomData_bmesh_init_pool(struct CustomData *data, int totelem, const char htype); +void CustomData_bmesh_init_pool(struct CustomData *data, int totelem, char htype); #ifndef NDEBUG +/** + * Debug check, used to assert when we expect layers to be in/out of sync. + * + * \param fallback: Use when there are no layers to handle, + * since callers may expect success or failure. + */ bool CustomData_from_bmeshpoly_test(CustomData *fdata, CustomData *ldata, bool fallback); #endif -/* Layer data validation. */ -bool CustomData_layer_validate(struct CustomDataLayer *layer, - const uint totitems, - const bool do_fixes); +/** + * Validate and fix data of \a layer, + * if possible (needs relevant callback in layer's type to be defined). + * + * \return True if some errors were found. + */ +bool CustomData_layer_validate(struct CustomDataLayer *layer, uint totitems, bool do_fixes); void CustomData_layers__print(struct CustomData *data); /* External file storage */ @@ -503,8 +620,8 @@ typedef void (*cd_datatransfer_interp)(const struct CustomDataTransferLayerMap * void *dest, const void **sources, const float *weights, - const int count, - const float mix_factor); + int count, + float mix_factor); /** * Fake CD_LAYERS (those are actually 'real' data stored directly into elements' structs, @@ -587,16 +704,41 @@ typedef struct CustomDataTransferLayerMap { cd_datatransfer_interp interp; } CustomDataTransferLayerMap; -/* Those functions assume src_n and dst_n layers of given type exist in resp. src and dst. */ +/** + * Those functions assume src_n and dst_n layers of given type exist in resp. src and dst. + */ void CustomData_data_transfer(const struct MeshPairRemap *me_remap, const CustomDataTransferLayerMap *laymap); /* .blend file I/O */ + +/** + * Prepare given custom data for file writing. + * + * \param data: the custom-data to tweak for .blend file writing (modified in place). + * \param r_write_layers: contains a reduced set of layers to be written to file, + * use it with #writestruct_at_address() + * (caller must free it if != \a write_layers_buff). + * + * \param write_layers_buff: An optional buffer for r_write_layers (to avoid allocating it). + * \param write_layers_size: The size of pre-allocated \a write_layer_buff. + * + * \warning After this funcion has ran, given custom data is no more valid from Blender POV + * (its `totlayer` is invalid). This function shall always be called with localized data + * (as it is in write_meshes()). + * + * \note `data->typemap` is not updated here, since it is always rebuilt on file read anyway. + * This means written `typemap` does not match written layers (as returned by \a r_write_layers). + * Trivial to fix is ever needed. + */ void CustomData_blend_write_prepare(struct CustomData *data, struct CustomDataLayer **r_write_layers, struct CustomDataLayer *write_layers_buff, size_t write_layers_size); +/** + * \param layers: The layers argument assigned by #CustomData_blend_write_prepare. + */ void CustomData_blend_write(struct BlendWriter *writer, struct CustomData *data, CustomDataLayer *layers, @@ -605,6 +747,14 @@ void CustomData_blend_write(struct BlendWriter *writer, struct ID *id); void CustomData_blend_read(struct BlendDataReader *reader, struct CustomData *data, int count); +#ifndef NDEBUG +struct DynStr; +/** Use to inspect mesh data when debugging. */ +void CustomData_debug_info_from_layers(const struct CustomData *data, + const char *indent, + struct DynStr *dynstr); +#endif /* NDEBUG */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_data_transfer.h b/source/blender/blenkernel/BKE_data_transfer.h index a2544e43c3d..42cf2256e8c 100644 --- a/source/blender/blenkernel/BKE_data_transfer.h +++ b/source/blender/blenkernel/BKE_data_transfer.h @@ -63,15 +63,19 @@ enum { DT_TYPE_POLY_ALL = DT_TYPE_UV | DT_TYPE_SHARP_FACE | DT_TYPE_FREESTYLE_FACE, }; -void BKE_object_data_transfer_dttypes_to_cdmask(const int dtdata_types, +void BKE_object_data_transfer_dttypes_to_cdmask(int dtdata_types, struct CustomData_MeshMasks *r_data_masks); -bool BKE_object_data_transfer_get_dttypes_capacity(const int dtdata_types, +/** + * Check what can do each layer type + * (if it is actually handled by transfer-data, if it supports advanced mixing. + */ +bool BKE_object_data_transfer_get_dttypes_capacity(int dtdata_types, bool *r_advanced_mixing, bool *r_threshold); -int BKE_object_data_transfer_get_dttypes_item_types(const int dtdata_types); +int BKE_object_data_transfer_get_dttypes_item_types(int dtdata_types); -int BKE_object_data_transfer_dttype_to_cdtype(const int dtdata_type); -int BKE_object_data_transfer_dttype_to_srcdst_index(const int dtdata_type); +int BKE_object_data_transfer_dttype_to_cdtype(int dtdata_type); +int BKE_object_data_transfer_dttype_to_srcdst_index(int dtdata_type); #define DT_DATATYPE_IS_VERT(_dt) \ ELEM(_dt, DT_TYPE_MDEFORMVERT, DT_TYPE_SHAPEKEY, DT_TYPE_SKIN, DT_TYPE_BWEIGHT_VERT) @@ -122,12 +126,18 @@ enum { #endif }; +/** + * Transfer data *layout* of selected types from source to destination object. + * By default, it only creates new data layers if needed on \a ob_dst. + * If \a use_delete is true, it will also delete data layers on \a ob_dst that do not match those + * from \a ob_src, to get (as much as possible) exact copy of source data layout. + */ void BKE_object_data_transfer_layout(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob_src, struct Object *ob_dst, - const int data_types, - const bool use_delete, + int data_types, + bool use_delete, const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX]); @@ -135,46 +145,46 @@ bool BKE_object_data_transfer_mesh(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob_src, struct Object *ob_dst, - const int data_types, - const bool use_create, - const int map_vert_mode, - const int map_edge_mode, - const int map_loop_mode, - const int map_poly_mode, + int data_types, + bool use_create, + int map_vert_mode, + int map_edge_mode, + int map_loop_mode, + int map_poly_mode, struct SpaceTransform *space_transform, - const bool auto_transform, - const float max_distance, - const float ray_radius, - const float islands_handling_precision, + bool auto_transform, + float max_distance, + float ray_radius, + float islands_handling_precision, const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX], - const int mix_mode, - const float mix_factor, + int mix_mode, + float mix_factor, const char *vgroup_name, - const bool invert_vgroup, + bool invert_vgroup, struct ReportList *reports); bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob_src, struct Object *ob_dst, struct Mesh *me_dst, - const int data_types, + int data_types, bool use_create, - const int map_vert_mode, - const int map_edge_mode, - const int map_loop_mode, - const int map_poly_mode, + int map_vert_mode, + int map_edge_mode, + int map_loop_mode, + int map_poly_mode, struct SpaceTransform *space_transform, - const bool auto_transform, - const float max_distance, - const float ray_radius, - const float islands_handling_precision, + bool auto_transform, + float max_distance, + float ray_radius, + float islands_handling_precision, const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX], - const int mix_mode, - const float mix_factor, + int mix_mode, + float mix_factor, const char *vgroup_name, - const bool invert_vgroup, + bool invert_vgroup, struct ReportList *reports); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_deform.h b/source/blender/blenkernel/BKE_deform.h index f4221d57428..ca0ca03f099 100644 --- a/source/blender/blenkernel/BKE_deform.h +++ b/source/blender/blenkernel/BKE_deform.h @@ -44,49 +44,101 @@ 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); +/** + * \note For historical reasons, the index starts at 1 rather than 0. + */ 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); +/** + * \note For historical reasons, the index starts at 1 rather than 0. + */ +void BKE_object_defgroup_active_index_set(struct Object *ob, 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); +bool BKE_id_defgroup_name_find(const struct ID *id, + const char *name, + int *r_index, + struct bDeformGroup **r_group); 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); struct bDeformGroup *BKE_object_defgroup_find_name(const struct Object *ob, const char *name); -int *BKE_object_defgroup_flip_map(const struct Object *ob, - int *flip_map_len, - const bool use_default); +/** + * \note caller must free. + */ +int *BKE_object_defgroup_flip_map(const struct Object *ob, int *flip_map_len, bool use_default); +/** + * \note caller must free. + */ int *BKE_object_defgroup_flip_map_single(const struct Object *ob, int *flip_map_len, - const bool use_default, + bool use_default, int defgroup); -int BKE_object_defgroup_flip_index(const struct Object *ob, int index, const bool use_default); +int BKE_object_defgroup_flip_index(const struct Object *ob, int index, bool use_default); int BKE_object_defgroup_name_index(const struct Object *ob, const char *name); void BKE_object_defgroup_unique_name(struct bDeformGroup *dg, struct Object *ob); -struct MDeformWeight *BKE_defvert_find_index(const struct MDeformVert *dv, const int defgroup); -struct MDeformWeight *BKE_defvert_ensure_index(struct MDeformVert *dv, const int defgroup); -void BKE_defvert_add_index_notest(struct MDeformVert *dv, int defgroup, const float weight); +struct MDeformWeight *BKE_defvert_find_index(const struct MDeformVert *dv, int defgroup); +/** + * Ensures that `dv` has a deform weight entry for the specified defweight group. + * + * \note this function is mirrored in editmesh_tools.c, for use for edit-vertices. + */ +struct MDeformWeight *BKE_defvert_ensure_index(struct MDeformVert *dv, int defgroup); +/** + * Adds the given vertex to the specified vertex group, with given weight. + * + * \warning this does NOT check for existing, assume caller already knows its not there. + */ +void BKE_defvert_add_index_notest(struct MDeformVert *dv, int defgroup, float weight); +/** + * Removes the given vertex from the vertex group. + * + * \warning This function frees the given #MDeformWeight, do not use it afterward! + */ void BKE_defvert_remove_group(struct MDeformVert *dvert, struct MDeformWeight *dw); void BKE_defvert_clear(struct MDeformVert *dvert); +/** + * \return The first group index shared by both deform verts + * or -1 if none are found. + */ int BKE_defvert_find_shared(const struct MDeformVert *dvert_a, const struct MDeformVert *dvert_b); -bool BKE_defvert_is_weight_zero(const struct MDeformVert *dvert, const int defgroup_tot); +/** + * \return true if has no weights. + */ +bool BKE_defvert_is_weight_zero(const struct MDeformVert *dvert, int defgroup_tot); void BKE_defvert_array_free_elems(struct MDeformVert *dvert, int totvert); void BKE_defvert_array_free(struct MDeformVert *dvert, int totvert); void BKE_defvert_array_copy(struct MDeformVert *dst, const struct MDeformVert *src, int totvert); -float BKE_defvert_find_weight(const struct MDeformVert *dvert, const int defgroup); -float BKE_defvert_array_find_weight_safe(const struct MDeformVert *dvert, - const int index, - const int defgroup); +float BKE_defvert_find_weight(const struct MDeformVert *dvert, int defgroup); +/** + * Take care with this the rationale is: + * - if the object has no vertex group. act like vertex group isn't set and return 1.0. + * - if the vertex group exists but the 'defgroup' isn't found on this vertex, _still_ return 0.0. + * + * This is a bit confusing, just saves some checks from the caller. + */ +float BKE_defvert_array_find_weight_safe(const struct MDeformVert *dvert, int index, int defgroup); +/** + * \return The total weight in all groups marked in the selection mask. + */ float BKE_defvert_total_selected_weight(const struct MDeformVert *dv, int defbase_tot, const bool *defbase_sel); +/** + * \return The representative weight of a multi-paint group, used for + * viewport colors and actual painting. + * + * Result equal to sum of weights with auto normalize, and average otherwise. + * Value is not clamped, since painting relies on multiplication being always + * commutative with the collective weight function. + */ float BKE_defvert_multipaint_collective_weight(const struct MDeformVert *dv, int defbase_tot, const bool *defbase_sel, @@ -96,9 +148,19 @@ float BKE_defvert_multipaint_collective_weight(const struct MDeformVert *dv, /* This much unlocked weight is considered equivalent to none. */ #define VERTEX_WEIGHT_LOCK_EPSILON 1e-6f +/** + * Computes the display weight for the lock relative weight paint mode. + * + * \return weight divided by 1-locked_weight with division by zero check + */ float BKE_defvert_calc_lock_relative_weight(float weight, float locked_weight, float unlocked_weight); +/** + * Computes the display weight for the lock relative weight paint mode, using weight data. + * + * \return weight divided by unlocked, or 1-locked_weight with division by zero check. + */ float BKE_defvert_lock_relative_weight(float weight, const struct MDeformVert *dv, int defbase_tot, @@ -106,79 +168,113 @@ float BKE_defvert_lock_relative_weight(float weight, const bool *defbase_unlocked); void BKE_defvert_copy(struct MDeformVert *dvert_dst, const struct MDeformVert *dvert_src); +/** + * Overwrite weights filtered by vgroup_subset. + * - do nothing if neither are set. + * - add destination weight if needed + */ void BKE_defvert_copy_subset(struct MDeformVert *dvert_dst, const struct MDeformVert *dvert_src, const bool *vgroup_subset, - const int vgroup_tot); + int vgroup_tot); +/** + * Overwrite weights filtered by vgroup_subset and with mirroring specified by the flip map + * - do nothing if neither are set. + * - add destination weight if needed + */ void BKE_defvert_mirror_subset(struct MDeformVert *dvert_dst, const struct MDeformVert *dvert_src, const bool *vgroup_subset, - const int vgroup_tot, + int vgroup_tot, const int *flip_map, - const int flip_map_len); + int flip_map_len); +/** + * Copy an index from one #MDeformVert to another. + * - do nothing if neither are set. + * - add destination weight if needed. + */ void BKE_defvert_copy_index(struct MDeformVert *dvert_dst, - const int defgroup_dst, + int defgroup_dst, const struct MDeformVert *dvert_src, - const int defgroup_src); + int defgroup_src); +/** + * Only sync over matching weights, don't add or remove groups + * warning, loop within loop. + */ void BKE_defvert_sync(struct MDeformVert *dvert_dst, const struct MDeformVert *dvert_src, - const bool use_ensure); + bool use_ensure); +/** + * be sure all flip_map values are valid + */ void BKE_defvert_sync_mapped(struct MDeformVert *dvert_dst, const struct MDeformVert *dvert_src, const int *flip_map, - const int flip_map_len, - const bool use_ensure); -void BKE_defvert_remap(struct MDeformVert *dvert, const int *map, const int map_len); -void BKE_defvert_flip(struct MDeformVert *dvert, const int *flip_map, const int flip_map_len); -void BKE_defvert_flip_merged(struct MDeformVert *dvert, - const int *flip_map, - const int flip_map_len); + int flip_map_len, + bool use_ensure); +/** + * be sure all flip_map values are valid + */ +void BKE_defvert_remap(struct MDeformVert *dvert, const int *map, int map_len); +void BKE_defvert_flip(struct MDeformVert *dvert, const int *flip_map, int flip_map_len); +void BKE_defvert_flip_merged(struct MDeformVert *dvert, const int *flip_map, int flip_map_len); void BKE_defvert_normalize(struct MDeformVert *dvert); +/** + * Same as #BKE_defvert_normalize but takes a bool array. + */ void BKE_defvert_normalize_subset(struct MDeformVert *dvert, const bool *vgroup_subset, - const int vgroup_tot); + int vgroup_tot); +/** + * Same as BKE_defvert_normalize() if the locked vgroup is not a member of the subset + */ void BKE_defvert_normalize_lock_single(struct MDeformVert *dvert, const bool *vgroup_subset, - const int vgroup_tot, - const uint def_nr_lock); + int vgroup_tot, + uint def_nr_lock); +/** + * Same as BKE_defvert_normalize() if no locked vgroup is a member of the subset + */ void BKE_defvert_normalize_lock_map(struct MDeformVert *dvert, const bool *vgroup_subset, - const int vgroup_tot, + int vgroup_tot, const bool *lock_flags, - const int defbase_tot); + int defbase_tot); /* Utilities to 'extract' a given vgroup into a simple float array, * for verts, but also edges/polys/loops. */ -void BKE_defvert_extract_vgroup_to_vertweights(struct MDeformVert *dvert, - const int defgroup, - const int num_verts, - float *r_weights, - const bool invert_vgroup); + +void BKE_defvert_extract_vgroup_to_vertweights( + struct MDeformVert *dvert, int defgroup, int num_verts, float *r_weights, bool invert_vgroup); +/** + * The following three make basic interpolation, + * using temp vert_weights array to avoid looking up same weight several times. + */ void BKE_defvert_extract_vgroup_to_edgeweights(struct MDeformVert *dvert, - const int defgroup, - const int num_verts, + int defgroup, + int num_verts, struct MEdge *edges, - const int num_edges, + int num_edges, float *r_weights, - const bool invert_vgroup); + bool invert_vgroup); void BKE_defvert_extract_vgroup_to_loopweights(struct MDeformVert *dvert, - const int defgroup, - const int num_verts, + int defgroup, + int num_verts, struct MLoop *loops, - const int num_loops, + int num_loops, float *r_weights, - const bool invert_vgroup); + bool invert_vgroup); void BKE_defvert_extract_vgroup_to_polyweights(struct MDeformVert *dvert, - const int defgroup, - const int num_verts, + int defgroup, + int num_verts, struct MLoop *loops, - const int num_loops, + int num_loops, struct MPoly *polys, - const int num_polys, + int num_polys, float *r_weights, - const bool invert_vgroup); + bool invert_vgroup); -void BKE_defvert_weight_to_rgb(float r_rgb[3], const float weight); +void BKE_defvert_weight_to_rgb(float r_rgb[3], float weight); void BKE_defvert_blend_write(struct BlendWriter *writer, int count, struct MDeformVert *dvlist); void BKE_defvert_blend_read(struct BlendDataReader *reader, diff --git a/source/blender/blenkernel/BKE_displist.h b/source/blender/blenkernel/BKE_displist.h index 8fb596a8096..6467ad36989 100644 --- a/source/blender/blenkernel/BKE_displist.h +++ b/source/blender/blenkernel/BKE_displist.h @@ -86,7 +86,7 @@ void BKE_displist_free(struct ListBase *lb); void BKE_displist_make_curveTypes(struct Depsgraph *depsgraph, const struct Scene *scene, struct Object *ob, - const bool for_render); + bool for_render); void BKE_displist_make_mball(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, @@ -94,14 +94,20 @@ void BKE_curve_calc_modifiers_pre(struct Depsgraph *depsgraph, struct Object *ob, struct ListBase *source_nurb, struct ListBase *target_nurb, - const bool for_render); + bool for_render); bool BKE_displist_surfindex_get( const struct DispList *dl, int a, int *b, int *p1, int *p2, int *p3, int *p4); +/** + * \param normal_proj: Optional normal that's used to project the scan-fill verts into 2D coords. + * Pass this along if known since it saves time calculating the normal. + * This is also used to initialize #DispList.nors (one normal per display list). + * \param flip_normal: Flip the normal (same as passing \a normal_proj negated). + */ void BKE_displist_fill(const struct ListBase *dispbase, struct ListBase *to, const float normal_proj[3], - const bool flip_normal); + bool flip_normal); float BKE_displist_calc_taper(struct Depsgraph *depsgraph, const struct Scene *scene, diff --git a/source/blender/blenkernel/BKE_duplilist.h b/source/blender/blenkernel/BKE_duplilist.h index 989b68f4ccb..303a83d921f 100644 --- a/source/blender/blenkernel/BKE_duplilist.h +++ b/source/blender/blenkernel/BKE_duplilist.h @@ -27,15 +27,18 @@ extern "C" { #endif struct Depsgraph; +struct ID; struct ListBase; struct Object; struct ParticleSystem; struct Scene; -struct ID; /* ---------------------------------------------------- */ /* Dupli-Geometry */ +/** + * \return a #ListBase of #DupliObject. + */ struct ListBase *object_duplilist(struct Depsgraph *depsgraph, struct Scene *sce, struct Object *ob); diff --git a/source/blender/blenkernel/BKE_dynamicpaint.h b/source/blender/blenkernel/BKE_dynamicpaint.h index 31f48be2c27..4b34a9490c4 100644 --- a/source/blender/blenkernel/BKE_dynamicpaint.h +++ b/source/blender/blenkernel/BKE_dynamicpaint.h @@ -64,41 +64,79 @@ typedef struct PaintWavePoint { short state; } PaintWavePoint; +/** + * Modifier call. Processes dynamic paint modifier step. + */ struct Mesh *dynamicPaint_Modifier_do(struct DynamicPaintModifierData *pmd, struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct Mesh *me); +/** + * Free whole dynamic-paint modifier. + */ void dynamicPaint_Modifier_free(struct DynamicPaintModifierData *pmd); void dynamicPaint_Modifier_free_runtime(struct DynamicPaintRuntime *runtime); void dynamicPaint_Modifier_copy(const struct DynamicPaintModifierData *pmd, struct DynamicPaintModifierData *tpmd, int flag); +/** + * Initialize modifier data. + */ bool dynamicPaint_createType(struct DynamicPaintModifierData *pmd, int type, struct Scene *scene); +/** + * Creates a new surface and adds it to the list + * If scene is null, frame range of 1-250 is used + * A pointer to this surface is returned. + */ struct DynamicPaintSurface *dynamicPaint_createNewSurface( struct DynamicPaintCanvasSettings *canvas, struct Scene *scene); +/** + * Clears surface data back to zero. + */ void dynamicPaint_clearSurface(const struct Scene *scene, struct DynamicPaintSurface *surface); +/** + * Completely (re)initializes surface (only for point cache types). + */ bool dynamicPaint_resetSurface(const struct Scene *scene, struct DynamicPaintSurface *surface); void dynamicPaint_freeSurface(const struct DynamicPaintModifierData *pmd, struct DynamicPaintSurface *surface); +/** + * Free canvas data. + */ void dynamicPaint_freeCanvas(struct DynamicPaintModifierData *pmd); +/* Free brush data */ void dynamicPaint_freeBrush(struct DynamicPaintModifierData *pmd); void dynamicPaint_freeSurfaceData(struct DynamicPaintSurface *surface); +/** + * Update cache frame range. + */ void dynamicPaint_cacheUpdateFrames(struct DynamicPaintSurface *surface); bool dynamicPaint_outputLayerExists(struct DynamicPaintSurface *surface, struct Object *ob, int output); +/** + * Change surface data to defaults on new type. + */ void dynamicPaintSurface_updateType(struct DynamicPaintSurface *surface); void dynamicPaintSurface_setUniqueName(struct DynamicPaintSurface *surface, const char *basename); +/** + * Get currently active surface (in user interface). + */ struct DynamicPaintSurface *get_activeSurface(struct DynamicPaintCanvasSettings *canvas); -/* image sequence baking */ +/** + * Image sequence baking. + */ int dynamicPaint_createUVSurface(struct Scene *scene, struct DynamicPaintSurface *surface, float *progress, short *do_update); +/** + * Calculate a single frame and included sub-frames for surface. + */ int dynamicPaint_calculateFrame(struct DynamicPaintSurface *surface, struct Depsgraph *depsgraph, struct Scene *scene, diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h index 2c24b1a5487..1da7ae3da8a 100644 --- a/source/blender/blenkernel/BKE_editmesh.h +++ b/source/blender/blenkernel/BKE_editmesh.h @@ -19,7 +19,7 @@ /** \file * \ingroup bke * - * The \link edmesh EDBM module\endlink is for editmode bmesh stuff. + * The \link edmesh EDBM module \endlink is for editmode bmesh stuff. * In contrast, this module is for code shared with blenkernel that's * only concerned with low level operations on the #BMEditMesh structure. */ @@ -62,14 +62,6 @@ typedef struct BMEditMesh { struct BMLoop *(*looptris)[3]; int tottri; - struct Mesh *mesh_eval_final, *mesh_eval_cage; - - /** Cached cage bounding box of `mesh_eval_cage` for selection. */ - struct BoundBox *bb_cage; - - /** Evaluated mesh data-mask. */ - CustomData_MeshMasks lastDataMask; - /** Selection mode (#SCE_SELECT_VERTEX, #SCE_SELECT_EDGE & #SCE_SELECT_FACE). */ short selectmode; /** The active material (assigned to newly created faces). */ @@ -102,12 +94,28 @@ void BKE_editmesh_looptri_calc_with_partial(BMEditMesh *em, struct BMPartialUpda void BKE_editmesh_looptri_and_normals_calc_with_partial(BMEditMesh *em, struct BMPartialUpdate *bmpinfo); +/** + * Performing the face normal calculation at the same time as tessellation + * gives a reasonable performance boost (approx ~20% faster). + */ void BKE_editmesh_looptri_and_normals_calc(BMEditMesh *em); +/** + * \note The caller is responsible for ensuring triangulation data, + * typically by calling #BKE_editmesh_looptri_calc. + */ BMEditMesh *BKE_editmesh_create(BMesh *bm); BMEditMesh *BKE_editmesh_copy(BMEditMesh *em); +/** + * \brief Return the #BMEditMesh for a given object + * + * \note this function assumes this is a mesh object, + * don't add NULL data check here. caller must do that + */ BMEditMesh *BKE_editmesh_from_object(struct Object *ob); -void BKE_editmesh_free_derived_caches(BMEditMesh *em); +/** + * \note Does not free the #BMEditMesh struct itself. + */ void BKE_editmesh_free_data(BMEditMesh *em); float (*BKE_editmesh_vert_coords_alloc(struct Depsgraph *depsgraph, @@ -124,8 +132,11 @@ const float (*BKE_editmesh_vert_coords_when_deformed(struct Depsgraph *depsgraph bool *r_is_alloc))[3]; void BKE_editmesh_lnorspace_update(BMEditMesh *em, struct Mesh *me); +/** + * If auto-smooth not already set, set it. + */ void BKE_editmesh_ensure_autosmooth(BMEditMesh *em, struct Mesh *me); -struct BoundBox *BKE_editmesh_cage_boundbox_get(BMEditMesh *em); +struct BoundBox *BKE_editmesh_cage_boundbox_get(struct Object *object, BMEditMesh *em); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_editmesh_bvh.h b/source/blender/blenkernel/BKE_editmesh_bvh.h index 69d1b4819f8..b4368ff363b 100644 --- a/source/blender/blenkernel/BKE_editmesh_bvh.h +++ b/source/blender/blenkernel/BKE_editmesh_bvh.h @@ -42,13 +42,13 @@ typedef bool (*BMBVHTree_FaceFilter)(struct BMFace *f, void *userdata); BMBVHTree *BKE_bmbvh_new_from_editmesh(struct BMEditMesh *em, int flag, const float (*cos_cage)[3], - const bool cos_cage_free); + bool cos_cage_free); BMBVHTree *BKE_bmbvh_new_ex(struct BMesh *bm, struct BMLoop *(*looptris)[3], int looptris_tot, int flag, const float (*cos_cage)[3], - const bool cos_cage_free, + bool cos_cage_free, bool (*test_fn)(struct BMFace *, void *user_data), void *user_data); BMBVHTree *BKE_bmbvh_new(struct BMesh *bm, @@ -56,14 +56,14 @@ BMBVHTree *BKE_bmbvh_new(struct BMesh *bm, int looptris_tot, int flag, const float (*cos_cage)[3], - const bool cos_cage_free); + bool cos_cage_free); void BKE_bmbvh_free(BMBVHTree *tree); struct BVHTree *BKE_bmbvh_tree_get(BMBVHTree *tree); struct BMFace *BKE_bmbvh_ray_cast(BMBVHTree *tree, const float co[3], const float dir[3], - const float radius, + float radius, float *r_dist, float r_hitout[3], float r_cagehit[3]); @@ -71,25 +71,29 @@ struct BMFace *BKE_bmbvh_ray_cast(BMBVHTree *tree, struct BMFace *BKE_bmbvh_ray_cast_filter(BMBVHTree *tree, const float co[3], const float dir[3], - const float radius, + float radius, float *r_dist, float r_hitout[3], float r_cagehit[3], BMBVHTree_FaceFilter filter_cb, void *filter_userdata); -/* find a vert closest to co in a sphere of radius dist_max */ -struct BMVert *BKE_bmbvh_find_vert_closest(BMBVHTree *tree, - const float co[3], - const float dist_max); -struct BMFace *BKE_bmbvh_find_face_closest(BMBVHTree *tree, - const float co[3], - const float dist_max); +/** + * Find a vert closest to co in a sphere of radius dist_max. + */ +struct BMVert *BKE_bmbvh_find_vert_closest(BMBVHTree *tree, const float co[3], float dist_max); +struct BMFace *BKE_bmbvh_find_face_closest(BMBVHTree *tree, const float co[3], float dist_max); +/** + * Overlap indices reference the looptri's. + */ struct BVHTreeOverlap *BKE_bmbvh_overlap(const BMBVHTree *bmtree_a, const BMBVHTree *bmtree_b, unsigned int *r_overlap_tot); +/** + * Overlap indices reference the looptri's. + */ struct BVHTreeOverlap *BKE_bmbvh_overlap_self(const BMBVHTree *bmtree, unsigned int *r_overlap_tot); diff --git a/source/blender/blenkernel/BKE_editmesh_tangent.h b/source/blender/blenkernel/BKE_editmesh_tangent.h index 0e3f063ae44..3b0569b869a 100644 --- a/source/blender/blenkernel/BKE_editmesh_tangent.h +++ b/source/blender/blenkernel/BKE_editmesh_tangent.h @@ -24,6 +24,13 @@ extern "C" { #endif +/** + * \see #BKE_mesh_calc_loop_tangent, same logic but used arrays instead of #BMesh data. + * + * \note This function is not so normal, its using #BMesh.ldata as input, + * but output's to #Mesh.ldata. + * This is done because #CD_TANGENT is cache data used only for drawing. + */ void BKE_editmesh_loop_tangent_calc(BMEditMesh *em, bool calc_active_tangent, const char (*tangent_names)[MAX_NAME], @@ -32,7 +39,7 @@ void BKE_editmesh_loop_tangent_calc(BMEditMesh *em, const float (*loop_normals)[3], const float (*vert_orco)[3], CustomData *dm_loopdata_out, - const uint dm_loopdata_out_len, + uint dm_loopdata_out_len, short *tangent_mask_curr_p); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_effect.h b/source/blender/blenkernel/BKE_effect.h index 3a964ddb1aa..f33ca2f03d1 100644 --- a/source/blender/blenkernel/BKE_effect.h +++ b/source/blender/blenkernel/BKE_effect.h @@ -113,16 +113,27 @@ struct PartDeflect *BKE_partdeflect_new(int type); struct PartDeflect *BKE_partdeflect_copy(const struct PartDeflect *pd_src); void BKE_partdeflect_free(struct PartDeflect *pd); +/** + * Create list of effector relations in the collection or entire scene. + * This is used by the depsgraph to build relations, as well as faster + * lookup of effectors during evaluation. + */ struct ListBase *BKE_effector_relations_create(struct Depsgraph *depsgraph, struct ViewLayer *view_layer, struct Collection *collection); void BKE_effector_relations_free(struct ListBase *lb); +/** + * Create effective list of effectors from relations built beforehand. + */ struct ListBase *BKE_effectors_create(struct Depsgraph *depsgraph, struct Object *ob_src, struct ParticleSystem *psys_src, struct EffectorWeights *weights, bool use_rotation); +/** + * Generic force/speed system, now used for particles, soft-bodies & dynamic-paint. + */ void BKE_effectors_apply(struct ListBase *effectors, struct ListBase *colliders, struct EffectorWeights *weights, @@ -146,15 +157,15 @@ float effector_falloff(struct EffectorCache *eff, struct EffectorData *efd, struct EffectedPoint *point, struct EffectorWeights *weights); -int closest_point_on_surface(struct SurfaceModifierData *surmd, - const float co[3], - float surface_co[3], - float surface_nor[3], - float surface_vel[3]); -int get_effector_data(struct EffectorCache *eff, - struct EffectorData *efd, - struct EffectedPoint *point, - int real_velocity); +bool closest_point_on_surface(struct SurfaceModifierData *surmd, + const float co[3], + float surface_co[3], + float surface_nor[3], + float surface_vel[3]); +bool get_effector_data(struct EffectorCache *eff, + struct EffectorData *efd, + struct EffectedPoint *point, + int real_velocity); /* required for particle_system.c */ #if 0 diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h index f494c2e30cc..121ec7f316b 100644 --- a/source/blender/blenkernel/BKE_fcurve.h +++ b/source/blender/blenkernel/BKE_fcurve.h @@ -133,20 +133,56 @@ typedef enum eFMI_Requirement_Flags { } eFMI_Requirement_Flags; /* Function Prototypes for FModifierTypeInfo's */ + +/** + * This function should always be used to get the appropriate type-info, + * as it has checks which prevent segfaults in some weird cases. + */ const FModifierTypeInfo *fmodifier_get_typeinfo(const struct FModifier *fcm); -const FModifierTypeInfo *get_fmodifier_typeinfo(const int type); +/** + * This function should be used for getting the appropriate type-info when only + * a F-Curve modifier type is known. + */ +const FModifierTypeInfo *get_fmodifier_typeinfo(int type); /* ---------------------- */ +/** + * Add a new F-Curve Modifier to the given F-Curve of a certain type. + */ struct FModifier *add_fmodifier(ListBase *modifiers, int type, struct FCurve *owner_fcu); +/** + * Make a copy of the specified F-Modifier. + */ struct FModifier *copy_fmodifier(const struct FModifier *src); +/** + * Duplicate all of the F-Modifiers in the Modifier stacks. + */ void copy_fmodifiers(ListBase *dst, const ListBase *src); +/** + * Remove and free the given F-Modifier from the given stack. + */ bool remove_fmodifier(ListBase *modifiers, struct FModifier *fcm); +/** + * Remove all of a given F-Curve's modifiers. + */ void free_fmodifiers(ListBase *modifiers); +/** + * Find the active F-Modifier. + */ struct FModifier *find_active_fmodifier(ListBase *modifiers); +/** + * Set the active F-Modifier. + */ void set_active_fmodifier(ListBase *modifiers, struct FModifier *fcm); +/** + * Do we have any modifiers which match certain criteria. + * + * \param mtype: Type of modifier (if 0, doesn't matter). + * \param acttype: Type of action to perform (if -1, doesn't matter). + */ bool list_has_suitable_fmodifier(ListBase *modifiers, int mtype, short acttype); typedef struct FModifiersStackStorage { @@ -156,17 +192,38 @@ typedef struct FModifiersStackStorage { } FModifiersStackStorage; uint evaluate_fmodifiers_storage_size_per_modifier(ListBase *modifiers); +/** + * Evaluate time modifications imposed by some F-Curve Modifiers. + * + * - This step acts as an optimization to prevent the F-Curve stack being evaluated + * several times by modifiers requesting the time be modified, as the final result + * would have required using the modified time + * - Modifiers only ever receive the unmodified time, as subsequent modifiers should be + * working on the 'global' result of the modified curve, not some localized segment, + * so \a evaltime gets set to whatever the last time-modifying modifier likes. + * - We start from the end of the stack, as only the last one matters for now. + * + * \param fcu: Can be NULL. + */ float evaluate_time_fmodifiers(FModifiersStackStorage *storage, ListBase *modifiers, struct FCurve *fcu, float cvalue, float evaltime); +/** + * Evaluates the given set of F-Curve Modifiers using the given data + * Should only be called after evaluate_time_fmodifiers() has been called. + */ void evaluate_value_fmodifiers(FModifiersStackStorage *storage, ListBase *modifiers, struct FCurve *fcu, float *cvalue, float evaltime); +/** + * Bake modifiers for given F-Curve to curve sample data, in the frame range defined + * by start and end (inclusive). + */ void fcurve_bake_modifiers(struct FCurve *fcu, int start, int end); int BKE_fcm_envelope_find_index(struct FCM_EnvelopeData *array, @@ -182,30 +239,64 @@ int BKE_fcm_envelope_find_index(struct FCM_EnvelopeData *array, /* -------- Data Management -------- */ struct FCurve *BKE_fcurve_create(void); +/** + * Frees the F-Curve itself too, so make sure #BLI_remlink is called before calling this. + */ void BKE_fcurve_free(struct FCurve *fcu); +/** + * Duplicate a F-Curve. + */ struct FCurve *BKE_fcurve_copy(const struct FCurve *fcu); - +/** + * Frees a list of F-Curves. + */ void BKE_fcurves_free(ListBase *list); +/** + * Duplicate a list of F-Curves. + */ void BKE_fcurves_copy(ListBase *dst, ListBase *src); +/** + * Callback used by lib_query to walk over all ID usages + * (mimics `foreach_id` callback of #IDTypeInfo structure). + */ void BKE_fcurve_foreach_id(struct FCurve *fcu, struct LibraryForeachIDData *data); -/* find matching F-Curve in the given list of F-Curves */ -struct FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_index); +/** + * Find the F-Curve affecting the given RNA-access path + index, + * in the list of F-Curves provided. + */ +struct FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], int array_index); +/** + * Quick way to loop over all f-curves of a given 'path'. + */ struct FCurve *BKE_fcurve_iter_step(struct FCurve *fcu_iter, const char rna_path[]); -/* high level function to get an fcurve from C without having the rna */ +/** + * High level function to get an f-curve from C without having the RNA. + */ struct FCurve *id_data_find_fcurve( ID *id, void *data, struct StructRNA *type, const char *prop_name, int index, bool *r_driven); -/* Get list of LinkData's containing pointers to the F-Curves which control the types of data - * indicated - * e.g. numMatches = BKE_fcurves_filter(matches, &act->curves, "pose.bones[", "MyFancyBone"); +/** + * Get list of LinkData's containing pointers to the F-curves + * which control the types of data indicated. + * e.g. `numMatches = BKE_fcurves_filter(matches, &act->curves, "pose.bones[", "MyFancyBone");` + * + * Lists: + * \param dst: list of LinkData's matching the criteria returned. + * List must be freed after use, and is assumed to be empty when passed. + * \param src: list of F-Curves to search through + * Filters: + * \param dataPrefix: i.e. `pose.bones[` or `nodes[`. + * \param dataName: name of entity within "" immediately following the prefix. */ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, const char *dataName); -/* Find an f-curve based on an rna property. */ +/** + * Find an f-curve based on an rna property. + */ struct FCurve *BKE_fcurve_find_by_rna(struct PointerRNA *ptr, struct PropertyRNA *prop, int rnaindex, @@ -213,8 +304,10 @@ struct FCurve *BKE_fcurve_find_by_rna(struct PointerRNA *ptr, struct bAction **r_action, bool *r_driven, bool *r_special); -/* Same as above, but takes a context data, - * temp hack needed for complex paths like texture ones. */ +/** + * Same as above, but takes a context data, + * temp hack needed for complex paths like texture ones. + */ struct FCurve *BKE_fcurve_find_by_rna_context_ui(struct bContext *C, struct PointerRNA *ptr, struct PropertyRNA *prop, @@ -224,65 +317,105 @@ struct FCurve *BKE_fcurve_find_by_rna_context_ui(struct bContext *C, bool *r_driven, bool *r_special); -/* Binary search algorithm for finding where to 'insert' BezTriple with given frame number. +/** + * 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(const struct BezTriple array[], - const float frame, - const int arraylen, + float frame, + int arraylen, bool *r_replace); /* fcurve_cache.c */ -/* Cached f-curve look-ups, use when this needs to be done many times. */ + +/** + * Cached f-curve look-ups, use when this needs to be done many times. + */ struct FCurvePathCache; struct FCurvePathCache *BKE_fcurve_pathcache_create(ListBase *list); void BKE_fcurve_pathcache_destroy(struct FCurvePathCache *fcache); struct FCurve *BKE_fcurve_pathcache_find(struct FCurvePathCache *fcache, const char rna_path[], - const int array_index); + int array_index); +/** + * Fill in an array of F-Curve, leave NULL when not found. + * + * \return The number of F-Curves found. + */ int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache, const char *rna_path, struct FCurve **fcurve_result, int fcurve_result_len); -/* get the time extents for F-Curve */ +/** + * Calculate the extents of F-Curve's keyframes. + */ bool BKE_fcurve_calc_range( - struct FCurve *fcu, float *min, float *max, const bool do_sel_only, const bool do_min_length); + struct FCurve *fcu, float *min, float *max, bool do_sel_only, bool do_min_length); -/* get the bounding-box extents for F-Curve */ +/** + * Calculate the extents of F-Curve's data. + */ bool BKE_fcurve_calc_bounds(struct FCurve *fcu, float *xmin, float *xmax, float *ymin, float *ymax, - const bool do_sel_only, - const bool include_handles); + bool do_sel_only, + bool include_handles); +/** + * Return an array of keyed frames, rounded to `interval`. + * + * \param interval: Set to 1.0 to round to whole keyframes, 0.5 for in-between key-frames, etc. + * + * \note An interval of zero could be supported (this implies no rounding at all), + * however this risks very small differences in float values being treated as separate keyframes. + */ float *BKE_fcurves_calc_keyed_frames_ex(struct FCurve **fcurve_array, - const int fcurve_array_len, - const float interval, + int fcurve_array_len, + float interval, int *r_frames_len); float *BKE_fcurves_calc_keyed_frames(struct FCurve **fcurve_array, - const int fcurve_array_len, + int fcurve_array_len, int *r_frames_len); +/** + * Set the index that stores the FCurve's active keyframe, assuming that \a active_bezt + * is already part of `fcu->bezt`. If NULL, set active keyframe index to "none." + */ void BKE_fcurve_active_keyframe_set(struct FCurve *fcu, const struct BezTriple *active_bezt); +/** + * Get the active keyframe index, with sanity checks for point bounds. + */ int BKE_fcurve_active_keyframe_index(const struct FCurve *fcu); -/* Move the indexed keyframe to the given value, and move the handles with it to ensure the slope - * remains the same. */ +/** + * Move the indexed keyframe to the given value, + * and move the handles with it to ensure the slope remains the same. + */ void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, float new_value); /* .............. */ -/* Are keyframes on F-Curve of any use (to final result, and to show in editors)? */ +/** + * Are keyframes on F-Curve of any use (to final result, and to show in editors)? + * Usability of keyframes refers to whether they should be displayed, + * and also whether they will have any influence on the final result. + */ bool BKE_fcurve_are_keyframes_usable(struct FCurve *fcu); -/* Can keyframes be added to F-Curve? */ +/** + * Can keyframes be added to F-Curve? + * Keyframes can only be added if they are already visible. + */ bool BKE_fcurve_is_keyframable(struct FCurve *fcu); bool BKE_fcurve_is_protected(struct FCurve *fcu); -/* The curve is an infinite cycle via Cycles modifier */ +/** + * Checks if the F-Curve has a Cycles modifier with simple settings + * that warrant transition smoothing. + */ bool BKE_fcurve_is_cyclic(struct FCurve *fcu); /* Type of infinite cycle for a curve. */ @@ -294,9 +427,19 @@ typedef enum eFCU_Cycle_Type { FCU_CYCLE_OFFSET, } eFCU_Cycle_Type; +/** + * Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. + */ eFCU_Cycle_Type BKE_fcurve_get_cycle_type(struct FCurve *fcu); -/* Recompute handles to neatly subdivide the prev-next range at bezt. */ +/** + * Recompute bezier handles of all three given BezTriples, so that `bezt` can be inserted between + * `prev` and `next` without changing the resulting curve shape. + * + * \param r_pdelta: return Y difference between `bezt` and the original curve value at its X + * position. + * \return Whether the split was successful. + */ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, @@ -304,12 +447,50 @@ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, /* -------- Curve Sanity -------- */ +/** + * This function recalculates the handles of an F-Curve. Acts based on selection with `SELECT` + * flag. To use a different flag, use #calchandles_fcurve_ex(). + * + * If the BezTriples have been rearranged, sort them first before using this. + */ void calchandles_fcurve(struct FCurve *fcu); +/** + * Variant of #calchandles_fcurve() that allows calculating based on a different select flag. + * + * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. + * Usually `SELECT`, but may want to use a different one at times + * (if caller does not operate on selection). + */ void calchandles_fcurve_ex(struct FCurve *fcu, eBezTriple_Flag handle_sel_flag); -void testhandles_fcurve(struct FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle); +/** + * Update handles, making sure the handle-types are valid (e.g. correctly deduced from an "Auto" + * type), and recalculating their position vectors. + * Use when something has changed handle positions. + * + * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`, + * but may want to use a different one at times (if caller does not operate on selection). + * \param use_handle: Check selection state of individual handles, otherwise always update both + * handles if the key is selected. + */ +void testhandles_fcurve(struct FCurve *fcu, eBezTriple_Flag sel_flag, bool use_handle); +/** + * This function sorts BezTriples so that they are arranged in chronological order, + * as tools working on F-Curves expect that the BezTriples are in order. + */ void sort_time_fcurve(struct FCurve *fcu); +/** + * This function tests if any BezTriples are out of order, thus requiring a sort. + */ bool test_time_fcurve(struct FCurve *fcu); +/** + * The length of each handle is not allowed to be more + * than the horizontal distance between (v1-v4). + * This is to prevent curve loops. + * + * This function is very similar to BKE_curve_correct_bezpart(), but allows a steeper tangent for + * more snappy animations. This is not desired for other areas in which curves are used, though. + */ void BKE_fcurve_correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2]); /* -------- Evaluation -------- */ @@ -321,8 +502,14 @@ float evaluate_fcurve_driver(struct PathResolvedRNA *anim_rna, struct FCurve *fcu, struct ChannelDriver *driver_orig, const struct AnimationEvalContext *anim_eval_context); +/** + * Checks if the curve has valid keys, drivers or modifiers that produce an actual curve. + */ bool BKE_fcurve_is_empty(struct FCurve *fcu); -/* evaluate fcurve and store value */ +/** + * Calculate the value of the given F-Curve at the given frame, + * and store it's value in #FCurve.curval. + */ float calculate_fcurve(struct PathResolvedRNA *anim_rna, struct FCurve *fcu, const struct AnimationEvalContext *anim_eval_context); @@ -331,27 +518,35 @@ float calculate_fcurve(struct PathResolvedRNA *anim_rna, /* -------- Defines -------- */ -/* Basic signature for F-Curve sample-creation function - * - fcu: the F-Curve being operated on - * - data: pointer to some specific data that may be used by one of the callbacks +/** + * Basic signature for F-Curve sample-creation function. + * + * \param fcu: the F-Curve being operated on. + * \param data: pointer to some specific data that may be used by one of the callbacks. */ typedef float (*FcuSampleFunc)(struct FCurve *fcu, void *data, float evaltime); /* ----- Sampling Callbacks ------ */ -/* Basic sampling callback which acts as a wrapper for evaluate_fcurve() */ +/** + * Basic sampling callback which acts as a wrapper for #evaluate_fcurve() + * 'data' arg here is unneeded here. + */ float fcurve_samplingcb_evalcurve(struct FCurve *fcu, void *data, float evaltime); /* -------- Main Methods -------- */ -/* Main API function for creating a set of sampled curve data, given some callback function +/** + * Main API function for creating a set of sampled curve data, given some callback function * used to retrieve the values to store. */ void fcurve_store_samples( struct FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb); -/* Convert baked/sampled fcurves into bezt/regular fcurves. */ -void fcurve_samples_to_keyframes(struct FCurve *fcu, const int start, const int end); +/** + * Convert baked/sampled f-curves into bezt/regular f-curves. + */ +void fcurve_samples_to_keyframes(struct FCurve *fcu, int start, int end); /* ************* F-Curve .blend file API ******************** */ diff --git a/source/blender/blenkernel/BKE_fcurve_driver.h b/source/blender/blenkernel/BKE_fcurve_driver.h index 5b4da4a78a4..20667c4dfba 100644 --- a/source/blender/blenkernel/BKE_fcurve_driver.h +++ b/source/blender/blenkernel/BKE_fcurve_driver.h @@ -66,34 +66,92 @@ struct PropertyRNA; /* ---------------------- */ +/** + * This frees the driver itself. + */ void fcurve_free_driver(struct FCurve *fcu); +/** + * This makes a copy of the given driver. + */ struct ChannelDriver *fcurve_copy_driver(const struct ChannelDriver *driver); +/** + * Copy driver variables from src_vars list to dst_vars list. + */ void driver_variables_copy(struct ListBase *dst_vars, const struct ListBase *src_vars); +/** + * Compute channel values for a rotational Transform Channel driver variable. + */ void BKE_driver_target_matrix_to_rot_channels( float mat[4][4], int auto_order, int rotation_mode, int channel, bool angles, float r_buf[4]); +/** + * Perform actual freeing driver variable and remove it from the given list. + */ void driver_free_variable(struct ListBase *variables, struct DriverVar *dvar); +/** + * Free the driver variable and do extra updates. + */ void driver_free_variable_ex(struct ChannelDriver *driver, struct DriverVar *dvar); +/** + * Change the type of driver variable. + */ void driver_change_variable_type(struct DriverVar *dvar, int type); +/** + * Validate driver variable name (after being renamed). + * + */ void driver_variable_name_validate(struct DriverVar *dvar); +/** + * Ensure the driver variable's name is unique. + * + * Assumes the driver variable has already been assigned to the driver, so that + * the `prev/next` pointers can be used to find the other variables. + */ +void driver_variable_unique_name(struct DriverVar *dvar); +/** + * Add a new driver variable. + */ struct DriverVar *driver_add_new_variable(struct ChannelDriver *driver); +/** + * Evaluate a Driver Variable to get a value that contributes to the final. + */ float driver_get_variable_value(struct ChannelDriver *driver, struct DriverVar *dvar); +/** + * Same as 'dtar_get_prop_val'. but get the RNA property. + */ bool driver_get_variable_property(struct ChannelDriver *driver, struct DriverTarget *dtar, struct PointerRNA *r_ptr, struct PropertyRNA **r_prop, int *r_index); +/** + * Check if the expression in the driver conforms to the simple subset. + */ bool BKE_driver_has_simple_expression(struct ChannelDriver *driver); +/** + * Check if the expression in the driver may depend on the current frame. + */ bool BKE_driver_expression_depends_on_time(struct ChannelDriver *driver); +/** + * Reset cached compiled expression data. + */ void BKE_driver_invalidate_expression(struct ChannelDriver *driver, bool expr_changed, bool varname_changed); +/** + * Evaluate an Channel-Driver to get a 'time' value to use + * instead of `anim_eval_context->eval_time`. + * + * - `anim_eval_context->eval_time` is the frame at which F-Curve is being evaluated. + * - Has to return a float value. + * - \a driver_orig is where we cache Python expressions, in case of COW + */ float evaluate_driver(struct PathResolvedRNA *anim_rna, struct ChannelDriver *driver, struct ChannelDriver *driver_orig, diff --git a/source/blender/blenkernel/BKE_fluid.h b/source/blender/blenkernel/BKE_fluid.h index 33ff6943514..88df8e52dca 100644 --- a/source/blender/blenkernel/BKE_fluid.h +++ b/source/blender/blenkernel/BKE_fluid.h @@ -49,7 +49,7 @@ void BKE_fluid_modifier_reset(struct FluidModifierData *fmd); void BKE_fluid_modifier_create_type_data(struct FluidModifierData *fmd); void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, struct FluidModifierData *tfmd, - const int flag); + int flag); bool BKE_fluid_reallocate_fluid(struct FluidDomainSettings *fds, int res[3], int free_old); void BKE_fluid_reallocate_copy_fluid(struct FluidDomainSettings *fds, @@ -64,6 +64,10 @@ void BKE_fluid_cache_free_all(struct FluidDomainSettings *fds, struct Object *ob void BKE_fluid_cache_free(struct FluidDomainSettings *fds, struct Object *ob, int cache_map); void BKE_fluid_cache_new_name_for_current_session(int maxlen, char *r_name); +/** + * Get fluid velocity and density at given coordinates. + * \returns fluid density or -1.0f if outside domain. + */ float BKE_fluid_get_velocity_at(struct Object *ob, float position[3], float velocity[3]); int BKE_fluid_get_data_flags(struct FluidDomainSettings *fds); @@ -72,8 +76,8 @@ void BKE_fluid_particle_system_create(struct Main *bmain, const char *pset_name, const char *parts_name, const char *psys_name, - const int psys_type); -void BKE_fluid_particle_system_destroy(struct Object *ob, const int particle_type); + int psys_type); +void BKE_fluid_particle_system_destroy(struct Object *ob, int particle_type); void BKE_fluid_cache_startframe_set(struct FluidDomainSettings *settings, int value); void BKE_fluid_cache_endframe_set(struct FluidDomainSettings *settings, int value); diff --git a/source/blender/blenkernel/BKE_freestyle.h b/source/blender/blenkernel/BKE_freestyle.h index 5e29665d728..3a4301aad6d 100644 --- a/source/blender/blenkernel/BKE_freestyle.h +++ b/source/blender/blenkernel/BKE_freestyle.h @@ -38,15 +38,19 @@ typedef struct FreestyleSettings FreestyleSettings; /* FreestyleConfig */ void BKE_freestyle_config_init(struct FreestyleConfig *config); -void BKE_freestyle_config_free(struct FreestyleConfig *config, const bool do_id_user); +void BKE_freestyle_config_free(struct FreestyleConfig *config, bool do_id_user); void BKE_freestyle_config_copy(struct FreestyleConfig *new_config, const struct FreestyleConfig *config, - const int flag); + int flag); /* FreestyleConfig.modules */ struct FreestyleModuleConfig *BKE_freestyle_module_add(struct FreestyleConfig *config); bool BKE_freestyle_module_delete(struct FreestyleConfig *config, struct FreestyleModuleConfig *module_conf); +/** + * Reinsert \a module_conf offset by \a direction from current position. + * \return if position of \a module_conf changed. + */ bool BKE_freestyle_module_move(struct FreestyleConfig *config, struct FreestyleModuleConfig *module_conf, int direction); diff --git a/source/blender/blenkernel/BKE_geometry_set.h b/source/blender/blenkernel/BKE_geometry_set.h index 17cdb9d6a42..2d6218f1036 100644 --- a/source/blender/blenkernel/BKE_geometry_set.h +++ b/source/blender/blenkernel/BKE_geometry_set.h @@ -39,6 +39,8 @@ typedef enum GeometryComponentType { GEO_COMPONENT_TYPE_CURVE = 4, } GeometryComponentType; +#define GEO_COMPONENT_TYPE_ENUM_SIZE 5 + void BKE_geometry_set_free(struct GeometrySet *geometry_set); bool BKE_object_has_geometry_set_instances(const struct Object *ob); diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index f182fb527e1..f92f33b2776 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -23,11 +23,11 @@ #include <atomic> #include <iostream> -#include "BLI_float3.hh" #include "BLI_float4x4.hh" #include "BLI_function_ref.hh" #include "BLI_hash.hh" #include "BLI_map.hh" +#include "BLI_math_vec_types.hh" #include "BLI_set.hh" #include "BLI_user_counter.hh" #include "BLI_vector_set.hh" @@ -62,7 +62,9 @@ class ComponentAttributeProviders; class GeometryComponent; /** - * This is the base class for specialized geometry component types. + * This is the base class for specialized geometry component types. A geometry component handles + * a user count to allow avoiding duplication when it is wrapped with #UserCounter. It also handles + * the attribute API, which generalizes storing and modifying generic information on a geometry. */ class GeometryComponent { private: @@ -91,99 +93,142 @@ class GeometryComponent { GeometryComponentType type() const; - /* Return true when any attribute with this name exists, including built in attributes. */ + /** + * Return true when any attribute with this name exists, including built in attributes. + */ 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. */ + /** + * 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::bke::AttributeIDRef &attribute_id) const; - /* Returns true when the geometry component supports this attribute domain. */ - bool attribute_domain_supported(const AttributeDomain domain) const; - /* Can only be used with supported domain types. */ - virtual int attribute_domain_size(const AttributeDomain domain) const; + /** + * Return true when the geometry component supports this attribute domain. + * \note Conceptually this function is static, the result is always the same for different + * instances of the same geometry component type. + */ + bool attribute_domain_supported(AttributeDomain domain) const; + /** + * Return the length of a specific domain, or 0 if the domain is not supported. + */ + virtual int attribute_domain_size(AttributeDomain domain) const; + /** + * Return true if the attribute name corresponds to a built-in attribute with a hardcoded domain + * and data type. + */ 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. */ + /** + * Get read-only access to an attribute with the given name or id, on the highest priority domain + * if there is a name collision. + * \return null if the attribute does not exist. + */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( 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. */ + /** + * Get read and write access to an attribute with the given name or id, on the highest priority + * domain if there is a name collision. + * \note #WriteAttributeLookup.tag_modified_fn must be called after modifying data. + * \return null if the attribute does not exist + */ blender::bke::WriteAttributeLookup attribute_try_get_for_write( const blender::bke::AttributeIDRef &attribute_id); - /* Get a read-only attribute for the domain based on the given attribute. This can be used to + /** + * Get a read-only attribute for the domain based on the given attribute. This can be used to * interpolate from one domain to another. - * Returns null if the interpolation is not implemented. */ - virtual std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( - std::unique_ptr<blender::fn::GVArray> varray, - const AttributeDomain from_domain, - const AttributeDomain to_domain) const; + * \return null if the interpolation is not implemented. + */ + blender::fn::GVArray attribute_try_adapt_domain(const blender::fn::GVArray &varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const + { + return this->attribute_try_adapt_domain_impl(varray, from_domain, to_domain); + } + /* Use instead of the method above when the type is known at compile time for type safety. */ + template<typename T> + blender::VArray<T> attribute_try_adapt_domain(const blender::VArray<T> &varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const + { + return this->attribute_try_adapt_domain_impl(varray, from_domain, to_domain) + .template typed<T>(); + } - /* Returns true when the attribute has been deleted. */ + /** Returns true when the attribute has been deleted. */ bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id); - /* Returns true when the attribute has been created. */ + /** Returns true when the attribute has been created. */ bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, + AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer); - /* Try to create the builtin attribute with the given name. No data type or domain has to be - * provided, because those are fixed for builtin attributes. */ + /** + * Try to create the builtin attribute with the given name. No data type or domain has to be + * provided, because those are fixed for builtin attributes. + */ bool attribute_try_create_builtin(const blender::StringRef attribute_name, const AttributeInit &initializer); blender::Set<blender::bke::AttributeIDRef> attribute_ids() const; + /** + * \return False if the callback explicitly returned false at any point, otherwise true, + * meaning the callback made it all the way through. + */ bool attribute_foreach(const AttributeForeachCallback callback) const; virtual bool is_empty() const; - /* Get a virtual array to read the data of an attribute on the given domain and data type. - * 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::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, - const CustomDataType data_type) const; + /** + * Get a virtual array that refers to the data of an attribute, interpolated to the given domain + * and converted to the data type. Returns null when the attribute does not exist or cannot be + * interpolated or converted. + */ + blender::fn::GVArray attribute_try_get_for_read(const blender::bke::AttributeIDRef &attribute_id, + AttributeDomain domain, + const CustomDataType data_type) const; - /* Get a virtual array to read the data of an attribute on the given domain. The data type is - * 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::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const; + /** + * Get a virtual array that refers to the data of an attribute, interpolated to the given domain. + * The data type is left unchanged. Returns null when the attribute does not exist or cannot be + * interpolated. + */ + blender::fn::GVArray attribute_try_get_for_read(const blender::bke::AttributeIDRef &attribute_id, + 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. */ + /** + * Get a virtual array that refers to the data of an attribute converted to the given data type. + * The attribute's domain is left unchanged. Returns null when the attribute does not exist or + * cannot be converted. + */ blender::bke::ReadAttributeLookup attribute_try_get_for_read( 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::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value = nullptr) const; - - /* Should be used instead of the method above when the requested data type is known at compile - * time for better type safety. */ + /** + * Get a virtual array that refers to the data of an attribute, interpolated to the given domain + * and converted to the data type. If that is not possible, the returned virtual array will + * contain a default value. This never returns null. + */ + blender::fn::GVArray attribute_get_for_read(const blender::bke::AttributeIDRef &attribute_id, + AttributeDomain domain, + const CustomDataType data_type, + const void *default_value = nullptr) const; + /* Use instead of the method above when the type is known at compile time for type safety. */ template<typename T> - blender::fn::GVArray_Typed<T> attribute_get_for_read( - const blender::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, - const T &default_value) const + blender::VArray<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_id, domain, type, &default_value); - return blender::fn::GVArray_Typed<T>(std::move(varray)); + return this->attribute_get_for_read(attribute_id, domain, type, &default_value) + .template typed<T>(); } /** @@ -198,19 +243,10 @@ class GeometryComponent { */ blender::bke::OutputAttribute attribute_try_get_for_output( const blender::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, + AttributeDomain domain, const CustomDataType data_type, const void *default_value = nullptr); - - /* Same as attribute_try_get_for_output, but should be used when the original values in the - * 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::bke::AttributeIDRef &attribute_id, - const AttributeDomain domain, - const CustomDataType data_type); - - /* Statically typed method corresponding to the equally named generic one. */ + /* Use instead of the method above when the type is known at compile time for type safety. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output( const blender::bke::AttributeIDRef &attribute_id, @@ -222,7 +258,17 @@ class GeometryComponent { return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value); } - /* Statically typed method corresponding to the equally named generic one. */ + /** + * Same as #attribute_try_get_for_output, but should be used when the original values in the + * attributes are not read, i.e. the attribute is used only for output. The can be faster because + * it can avoid interpolation and conversion of existing values. Since values are not read from + * this attribute, no default value is necessary. + */ + blender::bke::OutputAttribute attribute_try_get_for_output_only( + const blender::bke::AttributeIDRef &attribute_id, + AttributeDomain domain, + const CustomDataType data_type); + /* Use instead of the method above when the type is known at compile time for type safety. */ template<typename T> blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only( const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) @@ -234,25 +280,54 @@ class GeometryComponent { private: virtual const blender::bke::ComponentAttributeProviders *get_attribute_providers() const; + + virtual blender::fn::GVArray attribute_try_adapt_domain_impl(const blender::fn::GVArray &varray, + AttributeDomain from_domain, + AttributeDomain to_domain) const; }; template<typename T> inline constexpr bool is_geometry_component_v = std::is_base_of_v<GeometryComponent, T>; /** - * A geometry set contains zero or more geometry components. There is at most one component of each - * type. Individual components might be shared between multiple geometries. Shared components are - * copied automatically when write access is requested. + * A geometry set is a container for multiple kinds of geometry. It does not own geometry directly + * itself, instead geometry is owned by multiple #GeometryComponents, and the geometry set + * increases the user count of each component, so they avoid losing the data. This means + * individual components might be shared between multiple geometries and other code. Shared + * components are copied automatically when write access is requested. + * + * The components usually do not store data directly, but keep a reference to a data + * structure defined elsewhere. There is at most one component of each type: + * - #MeshComponent + * - #CurveComponent + * - #PointCloudComponent + * - #InstancesComponent + * - #VolumeComponent * * Copying a geometry set is a relatively cheap operation, because it does not copy the referenced - * geometry components. + * geometry components, so #GeometrySet can often be passed or moved by value. */ struct GeometrySet { private: using GeometryComponentPtr = blender::UserCounter<class GeometryComponent>; - blender::Map<GeometryComponentType, GeometryComponentPtr> components_; + /* Indexed by #GeometryComponentType. */ + std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_; public: + /** + * The methods are defaulted here so that they are not instantiated in every translation unit. + */ + GeometrySet(); + GeometrySet(const GeometrySet &other); + GeometrySet(GeometrySet &&other); + ~GeometrySet(); + GeometrySet &operator=(const GeometrySet &other); + GeometrySet &operator=(GeometrySet &&other); + + /** + * This method can only be used when the geometry set is mutable. It returns a mutable geometry + * component of the given type. + */ GeometryComponent &get_component_for_write(GeometryComponentType component_type); template<typename Component> Component &get_component_for_write() { @@ -260,6 +335,9 @@ struct GeometrySet { return static_cast<Component &>(this->get_component_for_write(Component::static_type)); } + /** + * Get the component of the given type. Might return null if the component does not exist yet. + */ const GeometryComponent *get_component_for_read(GeometryComponentType component_type) const; template<typename Component> const Component *get_component_for_read() const { @@ -281,19 +359,33 @@ struct GeometrySet { return this->remove(Component::static_type); } + /** + * Remove all geometry components with types that are not in the provided list. + */ void keep_only(const blender::Span<GeometryComponentType> component_types); void add(const GeometryComponent &component); + /** + * Get all geometry components in this geometry set for read-only access. + */ blender::Vector<const GeometryComponent *> get_components_for_read() const; - void compute_boundbox_without_instances(blender::float3 *r_min, blender::float3 *r_max) const; + bool compute_boundbox_without_instances(blender::float3 *r_min, blender::float3 *r_max) const; friend std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set); + /** + * Remove all geometry components from the geometry set. + */ void clear(); bool owns_direct_data() const; + /** + * Make sure that the geometry can be cached. This does not ensure ownership of object/collection + * instances. This is necessary because sometimes components only have read-only or editing + * access to their data, which might be freed later if this geometry set outlasts the data. + */ void ensure_owns_direct_data(); using AttributeForeachCallback = @@ -311,48 +403,139 @@ struct GeometrySet { 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)>; + /** + * 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 modify_geometry_sets(ForeachSubGeometryCallback callback); /* Utility methods for creation. */ + /** + * Create a new geometry set that only contains the given mesh. + */ static GeometrySet create_with_mesh( Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Create a new geometry set that only contains the given point cloud. + */ static GeometrySet create_with_pointcloud( PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Create a new geometry set that only contains the given curve. + */ static GeometrySet create_with_curve( CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); /* Utility methods for access. */ + /** + * Returns true when the geometry set has a mesh component that has a mesh. + */ bool has_mesh() const; + /** + * Returns true when the geometry set has a point cloud component that has a point cloud. + */ bool has_pointcloud() const; + /** + * Returns true when the geometry set has an instances component that has at least one instance. + */ bool has_instances() const; + /** + * Returns true when the geometry set has a volume component that has a volume. + */ bool has_volume() const; + /** + * Returns true when the geometry set has a curve component that has a curve. + */ bool has_curve() const; + /** + * Returns true when the geometry set has any data that is not an instance. + */ bool has_realized_data() const; + /** + * Return true if the geometry set has any component that isn't empty. + */ bool is_empty() const; + /** + * Returns a read-only mesh or null. + */ const Mesh *get_mesh_for_read() const; + /** + * Returns a read-only point cloud of null. + */ const PointCloud *get_pointcloud_for_read() const; + /** + * Returns a read-only volume or null. + */ const Volume *get_volume_for_read() const; + /** + * Returns a read-only curve or null. + */ const CurveEval *get_curve_for_read() const; + /** + * Returns a mutable mesh or null. No ownership is transferred. + */ Mesh *get_mesh_for_write(); + /** + * Returns a mutable point cloud or null. No ownership is transferred. + */ PointCloud *get_pointcloud_for_write(); + /** + * Returns a mutable volume or null. No ownership is transferred. + */ Volume *get_volume_for_write(); + /** + * Returns a mutable curve or null. No ownership is transferred. + */ CurveEval *get_curve_for_write(); /* Utility methods for replacement. */ + /** + * Clear the existing mesh and replace it with the given one. + */ void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Clear the existing point cloud and replace with the given one. + */ void replace_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Clear the existing volume and replace with the given one. + */ void replace_volume(Volume *volume, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Clear the existing curve and replace it with the given one. + */ void replace_curve(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + + private: + /** + * Retrieve the pointer to a component without creating it if it does not exist, + * unlike #get_component_for_write. + */ + 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. */ +/** + * A geometry component that can store a mesh, storing the #Mesh data structure. + * + * Attributes are stored in the mesh itself, on any of the four attribute domains. Generic + * attributes are stored in contiguous arrays, but often built-in attributes are stored in an + * array of structs fashion for historical reasons, requiring more complex attribute access. + */ class MeshComponent : public GeometryComponent { private: Mesh *mesh_ = nullptr; @@ -365,17 +548,28 @@ class MeshComponent : public GeometryComponent { void clear(); bool has_mesh() const; + /** + * Clear the component and replace it with the new mesh. + */ void replace(Mesh *mesh, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Return the mesh and clear the component. The caller takes over responsibility for freeing the + * mesh (if the component was responsible before). + */ Mesh *release(); + /** + * 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 *get_for_read() const; + /** + * Get the mesh from this component. This method can only be used when the component is mutable, + * i.e. it is not shared. The returned mesh can be modified. No ownership is transferred. + */ Mesh *get_for_write(); - int attribute_domain_size(const AttributeDomain domain) const final; - std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( - std::unique_ptr<blender::fn::GVArray> varray, - const AttributeDomain from_domain, - const AttributeDomain to_domain) const final; + int attribute_domain_size(AttributeDomain domain) const final; bool is_empty() const final; @@ -386,9 +580,22 @@ class MeshComponent : public GeometryComponent { private: const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; + + blender::fn::GVArray attribute_try_adapt_domain_impl(const blender::fn::GVArray &varray, + AttributeDomain from_domain, + AttributeDomain to_domain) const final; }; -/** A geometry component that stores a point cloud. */ +/** + * A geometry component that stores a point cloud, corresponding to the #PointCloud data structure. + * While a point cloud is technically a subset of a mesh in some respects, it is useful because of + * its simplicity, partly on a conceptual level for the user, but also in the code, though partly + * for historical reasons. Point clouds can also be rendered in special ways, based on the built-in + * `radius` attribute. + * + * Attributes on point clouds are all stored in contiguous arrays in its #CustomData, + * which makes them efficient to process, relative to some legacy built-in mesh attributes. + */ class PointCloudComponent : public GeometryComponent { private: PointCloud *pointcloud_ = nullptr; @@ -401,14 +608,31 @@ class PointCloudComponent : public GeometryComponent { void clear(); bool has_pointcloud() const; + /** + * Clear the component and replace it with the new point cloud. + */ void replace(PointCloud *pointcloud, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Return the point cloud and clear the component. The caller takes over responsibility for + * freeing the point cloud (if the component was responsible before). + */ PointCloud *release(); + /** + * Get the point cloud from this component. This method can be used by multiple threads at the + * same time. Therefore, the returned point cloud should not be modified. No ownership is + * transferred. + */ const PointCloud *get_for_read() const; + /** + * Get the point cloud from this component. This method can only be used when the component is + * mutable, i.e. it is not shared. The returned point cloud can be modified. No ownership is + * transferred. + */ PointCloud *get_for_write(); - int attribute_domain_size(const AttributeDomain domain) const final; + int attribute_domain_size(AttributeDomain domain) const final; bool is_empty() const final; @@ -421,7 +645,13 @@ class PointCloudComponent : public GeometryComponent { const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; -/** A geometry component that stores curve data, in other words, a group of splines. */ +/** + * A geometry component that stores curve data, in other words, a group of splines. + * Curves are stored differently than other geometry components, because the data structure used + * here does not correspond exactly to the #Curve DNA data structure. A #CurveEval is stored here + * instead, though the component does give access to a #Curve for interfacing with render engines + * and other areas of Blender that expect to use a data-block with an #ID. + */ class CurveComponent : public GeometryComponent { private: CurveEval *curve_ = nullptr; @@ -443,31 +673,42 @@ class CurveComponent : public GeometryComponent { void clear(); bool has_curve() const; + /** + * Clear the component and replace it with the new curve. + */ void replace(CurveEval *curve, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); CurveEval *release(); const CurveEval *get_for_read() const; CurveEval *get_for_write(); - int attribute_domain_size(const AttributeDomain domain) const final; - std::unique_ptr<blender::fn::GVArray> attribute_try_adapt_domain( - std::unique_ptr<blender::fn::GVArray> varray, - const AttributeDomain from_domain, - const AttributeDomain to_domain) const final; + int attribute_domain_size(AttributeDomain domain) const final; bool is_empty() const final; bool owns_direct_data() const override; void ensure_owns_direct_data() override; + /** + * Create empty curve data used for rendering the spline's wire edges. + * \note See comment on #curve_for_render_ for further explanation. + */ const Curve *get_curve_for_render() const; static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_CURVE; private: const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; + + blender::fn::GVArray attribute_try_adapt_domain_impl(const blender::fn::GVArray &varray, + AttributeDomain from_domain, + AttributeDomain to_domain) const final; }; +/** + * Holds a reference to conceptually unique geometry or a pointer to object/collection data + * that is is instanced with a transform in #InstancesComponent. + */ class InstanceReference { public: enum class Type { @@ -590,7 +831,19 @@ class InstanceReference { } }; -/** A geometry component that stores instances. */ +/** + * A geometry component that stores instances. The instance data can be any type described by + * #InstanceReference. Geometry instances can even contain instances themselves, for nested + * instancing. Each instance has an index into an array of unique instance data, and a transform. + * The component can also store generic attributes for each instance. + * + * The component works differently from other geometry components in that it stores + * data about instancing directly, rather than owning a pointer to a separate data structure. + * + * This component is not responsible for handling the interface to a render engine, or other + * areas that work with all visible geometry, that is handled by the dependency graph iterator + * (see `DEG_depsgraph_query.h`). + */ class InstancesComponent : public GeometryComponent { private: /** @@ -603,19 +856,16 @@ class InstancesComponent : public GeometryComponent { blender::Vector<int> instance_reference_handles_; /** Transformation of the instances. */ blender::Vector<blender::float4x4> instance_transforms_; - /** - * IDs of the instances. They are used for consistency over multiple frames for things like - * motion blur. - */ - blender::Vector<int> instance_ids_; - /* These almost unique ids are generated based on `ids_`, which might not contain unique ids at - * all. They are *almost* unique, because under certain very unlikely circumstances, they are not - * unique. Code using these ids should not crash when they are not unique but can generally - * expect them to be unique. */ + /* These almost unique ids are generated based on the `id` attribute, which might not contain + * unique ids at all. They are *almost* unique, because under certain very unlikely + * circumstances, they are not unique. Code using these ids should not crash when they are not + * unique but can generally expect them to be unique. */ mutable std::mutex almost_unique_ids_mutex_; mutable blender::Array<int> almost_unique_ids_; + blender::bke::CustomDataAttributes attributes_; + public: InstancesComponent(); ~InstancesComponent() = default; @@ -624,30 +874,63 @@ class InstancesComponent : public GeometryComponent { void clear(); void reserve(int min_capacity); + /** + * Resize the transform, handles, and attributes to the specified capacity. + * + * \note This function should be used carefully, only when it's guaranteed + * that the data will be filled. + */ void resize(int capacity); + /** + * 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 add_reference(const InstanceReference &reference); - void add_instance(int instance_handle, const blender::float4x4 &transform, const int id = -1); + /** + * Add a reference to the instance reference with an index specified by the #instance_handle + * argument. For adding many instances, using #resize and accessing the transform array directly + * is preferred. + */ + void add_instance(int instance_handle, const blender::float4x4 &transform); blender::Span<InstanceReference> references() const; void remove_unused_references(); + /** + * 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 ensure_geometry_instances(); - GeometrySet &geometry_set_from_reference(const int reference_index); + /** + * 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 &geometry_set_from_reference(int reference_index); blender::Span<int> instance_reference_handles() const; blender::MutableSpan<int> instance_reference_handles(); blender::MutableSpan<blender::float4x4> instance_transforms(); blender::Span<blender::float4x4> instance_transforms() const; - blender::MutableSpan<int> instance_ids(); - blender::Span<int> instance_ids() const; int instances_amount() const; int references_amount() const; + /** + * Remove the indices that are not contained in the mask input, and remove unused instance + * references afterwards. + */ + void remove_instances(const blender::IndexMask mask); + blender::Span<int> almost_unique_ids() const; - int attribute_domain_size(const AttributeDomain domain) const final; + blender::bke::CustomDataAttributes &attributes(); + const blender::bke::CustomDataAttributes &attributes() const; + + int attribute_domain_size(AttributeDomain domain) const final; void foreach_referenced_geometry( blender::FunctionRef<void(const GeometrySet &geometry_set)> callback) const; @@ -663,7 +946,11 @@ class InstancesComponent : public GeometryComponent { const blender::bke::ComponentAttributeProviders *get_attribute_providers() const final; }; -/** A geometry component that stores volume grids. */ +/** + * A geometry component that stores volume grids, corresponding to the #Volume data structure. + * This component does not implement an attribute API, partly because storage of sparse volume + * information in grids is much more complicated than it is for other types + */ class VolumeComponent : public GeometryComponent { private: Volume *volume_ = nullptr; @@ -676,10 +963,26 @@ class VolumeComponent : public GeometryComponent { void clear(); bool has_volume() const; + /** + * Clear the component and replace it with the new volume. + */ void replace(Volume *volume, GeometryOwnershipType ownership = GeometryOwnershipType::Owned); + /** + * Return the volume and clear the component. The caller takes over responsibility for freeing + * the volume (if the component was responsible before). + */ Volume *release(); + /** + * Get the volume from this component. This method can be used by multiple threads at the same + * time. Therefore, the returned volume should not be modified. No ownership is transferred. + */ const Volume *get_for_read() const; + /** + * Get the volume from this component. This method can only be used when the component is + * mutable, i.e. it is not shared. The returned volume can be modified. No ownership is + * transferred. + */ Volume *get_for_write(); bool owns_direct_data() const override; @@ -712,14 +1015,28 @@ class GeometryComponentFieldContext : public fn::FieldContext { } }; -class AttributeFieldInput : public fn::FieldInput { +class GeometryFieldInput : public fn::FieldInput { + public: + using fn::FieldInput::FieldInput; + + GVArray get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &scope) const override; + + virtual GVArray get_varray_for_context(const GeometryComponent &component, + AttributeDomain domain, + IndexMask mask) const = 0; +}; + +class AttributeFieldInput : public GeometryFieldInput { private: std::string name_; public: AttributeFieldInput(std::string name, const CPPType &type) - : fn::FieldInput(type, name), name_(std::move(name)) + : GeometryFieldInput(type, name), name_(std::move(name)) { + category_ = Category::NamedAttribute; } template<typename T> static fn::Field<T> Create(std::string name) @@ -734,9 +1051,26 @@ class AttributeFieldInput : public fn::FieldInput { return name_; } - const GVArray *get_varray_for_context(const fn::FieldContext &context, - IndexMask mask, - ResourceScope &scope) const override; + GVArray get_varray_for_context(const GeometryComponent &component, + AttributeDomain domain, + IndexMask mask) 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 GeometryFieldInput { + public: + IDAttributeFieldInput() : GeometryFieldInput(CPPType::get<int>()) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + AttributeDomain domain, + IndexMask mask) const override; std::string socket_inspection_name() const override; @@ -744,31 +1078,62 @@ class AttributeFieldInput : public fn::FieldInput { bool is_equal_to(const fn::FieldNode &other) const override; }; -class AnonymousAttributeFieldInput : public fn::FieldInput { +VArray<float3> curve_normals_varray(const CurveComponent &component, const AttributeDomain domain); + +VArray<float3> mesh_normals_varray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain); + +class NormalFieldInput : public GeometryFieldInput { + public: + NormalFieldInput() : GeometryFieldInput(CPPType::get<float3>()) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) 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 GeometryFieldInput { 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) - : fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id)) + AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, + const CPPType &type, + std::string producer_name) + : GeometryFieldInput(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) + 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); + 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; + GVArray get_varray_for_context(const GeometryComponent &component, + AttributeDomain domain, + IndexMask mask) const override; std::string socket_inspection_name() const override; diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh index 653450c7d8e..98120b07f2d 100644 --- a/source/blender/blenkernel/BKE_geometry_set_instances.hh +++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh @@ -21,6 +21,11 @@ namespace blender::bke { /** + * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. + */ +GeometrySet object_get_evaluated_geometry_set(const Object &object); + +/** * Used to keep track of a group of instances using the same geometry data. */ struct GeometryInstanceGroup { @@ -39,12 +44,19 @@ struct GeometryInstanceGroup { Vector<float4x4> transforms; }; +/** + * Return flattened vector of the geometry component's recursive instances. I.e. all collection + * instances and object instances will be expanded into the instances of their geometry components. + * Even the instances in those geometry components' will be included. + * + * \note For convenience (to avoid duplication in the caller), the returned vector also contains + * the argument geometry set. + * + * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. + */ 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); - /** * 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 diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index 7696b5c0189..0c17636be03 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -37,36 +37,61 @@ struct Main; typedef struct Global { - /** Active pointers. */ + /** + * Data for the current active blend file. + * + * Note that `CTX_data_main(C)` should be used where possible. + * Otherwise access via #G_MAIN. + */ struct Main *main; - /** Strings: last saved */ - char ima[1024], lib[1024]; /* 1024 = FILE_MAX */ - - /** When set: `G_MAIN->name` contains valid relative base path. */ - bool relbase_valid; - bool file_loaded; - bool save_over; + /** Last saved location for images. */ + char ima[1024]; /* 1024 = FILE_MAX */ + /** Last used location for library link/append. */ + char lib[1024]; - /** Strings of recent opened files. */ + /** + * Strings of recently opened files to show in the file menu. + * A list of #RecentFile read from #BLENDER_HISTORY_FILE. + */ struct ListBase recent_files; - /** Has escape been pressed or Ctrl+C pressed in background mode, used for render quit. */ + /** + * Set when Escape been pressed or `Ctrl-C` pressed in background mode. + * Used for render quit and some other background tasks such as baking. + */ bool is_break; + /** + * Blender is running without any Windows or OpenGLES context. + * Typically set by the `--background` command-line argument. + * + * Also enabled when build defines `WITH_PYTHON_MODULE` or `WITH_HEADLESS` are set + * (which use background mode by definition). + */ bool background; + + /** + * Skip reading the startup file and user preferences. + * Also disable saving the preferences on exit (see #G_FLAG_USERPREF_NO_SAVE_ON_EXIT), + * see via the command line argument: `--factory-startup`. + */ bool factory_startup; + /** + * Set when the user is interactively moving (transforming) content. + * see: #G_TRANSFORM_OBJ and related flags. + */ short moving; - /** To indicate render is busy, prevent render-window events etc. */ + /** To indicate render is busy, prevent render-window events, animation playback etc. */ bool is_rendering; /** * Debug value, can be set from the UI and python, used for testing nonstandard features. * DO NOT abuse it with generic checks like `if (G.debug_value > 0)`. Do not use it as bitflags. * Only precise specific values should be checked for, to avoid unpredictable side-effects. - * Please document here the value(s) you are using (or a range of values reserved to some area). + * Please document here the value(s) you are using (or a range of values reserved to some area): * * -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). @@ -79,28 +104,55 @@ typedef struct Global { * * 1234: Disable new dyntopo code fixing skinny faces generation (04/2015). * * 3001: Enable additional Fluid modifier (Mantaflow) options (02/2020). * * 4000: Line Art state output and debugging logs (03/2021). + * * 4001: Mesh topology information in the spreadsheet (01/2022). * * 16384 and above: Reserved for python (add-ons) usage. */ short debug_value; - /** Saved to the blend file as #FileGlobal.globalf, - * however this is now only used for runtime options. */ + /** + * Saved to the blend file as #FileGlobal.globalf + * + * \note Currently this is only used for runtime options, adding flags to #G_FLAG_ALL_READFILE + * will cause them to be written and read to files. + */ int f; struct { - /** Logging vars (different loggers may use). */ + /** + * Logging vars (different loggers may use). + * Set via `--log-level` command line argument. + */ int level; - /** FILE handle or use stderr (we own this so close when done). */ + /** + * FILE handle or use `stderr` (we own this so close when done). + * Set via `--log-file` command line argument. + */ void *file; } log; - /** debug flag, #G_DEBUG, #G_DEBUG_PYTHON & friends, set python or command line args */ + /** + * Debug flag, #G_DEBUG, #G_DEBUG_PYTHON & friends, set via: + * - Command line arguments: `--debug`, `--debug-memory` ... etc. + * - Python API: `bpy.app.debug`, `bpy.app.debug_memory` ... etc. + */ int debug; - /** This variable is written to / read from #FileGlobal.fileflags */ + /** + * Control behavior of file reading/writing. + * + * This variable is written to / read from #FileGlobal.fileflags. + * See: #G_FILE_COMPRESS and related flags. + */ int fileflags; - /** Message to use when auto execution fails. */ + /** + * Message to show when loading a `.blend` file attempts to execute + * a Python script or driver-expression when doing so is disallowed. + * + * Set when `(G.f & G_FLAG_SCRIPT_AUTOEXEC_FAIL) == 0`, + * so users can be alerted to the reason why the file may not be behaving as expected. + * Typically Python drivers. + */ char autoexec_fail[200]; } Global; @@ -211,6 +263,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 what's 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 b58317f4815..885d0c2fd90 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -86,90 +86,244 @@ struct bGPdata; /* ------------ Grease-Pencil API ------------------ */ +/* clean vertex groups weights */ void BKE_gpencil_free_point_weights(struct MDeformVert *dvert); void BKE_gpencil_free_stroke_weights(struct bGPDstroke *gps); void BKE_gpencil_free_stroke_editcurve(struct bGPDstroke *gps); +/* free stroke, doesn't unlink from any listbase */ void BKE_gpencil_free_stroke(struct bGPDstroke *gps); +/* Free strokes belonging to a gp-frame */ bool BKE_gpencil_free_strokes(struct bGPDframe *gpf); +/* Free all of a gp-layer's frames */ void BKE_gpencil_free_frames(struct bGPDlayer *gpl); +/* Free all of the gp-layers for a viewport (list should be &gpd->layers or so) */ void BKE_gpencil_free_layers(struct ListBase *list); +/** Free (or release) any data used by this grease pencil (does not free the gpencil itself). */ void BKE_gpencil_free_data(struct bGPdata *gpd, bool free_all); +/** + * Delete grease pencil evaluated data + * \param gpd_eval: Grease pencil data-block + */ void BKE_gpencil_eval_delete(struct bGPdata *gpd_eval); void BKE_gpencil_free_layer_masks(struct bGPDlayer *gpl); +/** + * Tag data-block for depsgraph update. + * Wrapper to avoid include Depsgraph tag functions in other modules. + * \param gpd: Grease pencil data-block. + */ void BKE_gpencil_tag(struct bGPdata *gpd); void BKE_gpencil_batch_cache_dirty_tag(struct bGPdata *gpd); void BKE_gpencil_batch_cache_free(struct bGPdata *gpd); +/** + * Ensure selection status of stroke is in sync with its points. + * \param gps: Grease pencil stroke + */ void BKE_gpencil_stroke_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps); void BKE_gpencil_curve_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps); +/* Assign unique stroke ID for selection. */ void BKE_gpencil_stroke_select_index_set(struct bGPdata *gpd, struct bGPDstroke *gps); +/* Reset unique stroke ID for selection. */ void BKE_gpencil_stroke_select_index_reset(struct bGPDstroke *gps); +/** + * Add a new gp-frame to the given layer. + * \param gpl: Grease pencil layer + * \param cframe: Frame number + * \return Pointer to new frame + */ struct bGPDframe *BKE_gpencil_frame_addnew(struct bGPDlayer *gpl, int cframe); +/** + * Add a copy of the active gp-frame to the given layer. + * \param gpl: Grease pencil layer + * \param cframe: Frame number + * \return Pointer to new frame + */ struct bGPDframe *BKE_gpencil_frame_addcopy(struct bGPDlayer *gpl, int cframe); +/** + * Add a new gp-layer and make it the active layer. + * \param gpd: Grease pencil data-block + * \param name: Name of the layer + * \param setactive: Set as active + * \param add_to_header: Used to force the layer added at header + * \return Pointer to new layer + */ struct bGPDlayer *BKE_gpencil_layer_addnew(struct bGPdata *gpd, const char *name, - const bool setactive, - const bool add_to_header); + bool setactive, + bool add_to_header); +/** + * Add a new grease pencil data-block. + * \param bmain: Main pointer + * \param name: Name of the datablock + * \return Pointer to new data-block + */ struct bGPdata *BKE_gpencil_data_addnew(struct Main *bmain, const char name[]); -struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src, - const bool dup_strokes); +/** + * Make a copy of a given gpencil frame. + * \param gpf_src: Source grease pencil frame + * \return Pointer to new frame + */ +struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src, bool dup_strokes); +/** + * Make a copy of a given gpencil layer. + * \param gpl_src: Source grease pencil layer + * \return Pointer to new layer + */ struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src, - const bool dup_frames, - const bool dup_strokes); + bool dup_frames, + bool dup_strokes); +/** + * Make a copy of a given gpencil layer settings. + */ void BKE_gpencil_layer_copy_settings(const struct bGPDlayer *gpl_src, struct bGPDlayer *gpl_dst); +/** + * Make a copy of strokes between gpencil frames. + * \param gpf_src: Source grease pencil frame + * \param gpf_dst: Destination grease pencil frame + */ void BKE_gpencil_frame_copy_strokes(struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst); +/* Create a hash with the list of selected frame number. */ void BKE_gpencil_frame_selected_hash(struct bGPdata *gpd, struct GHash *r_list); +/* Make a copy of a given gpencil stroke editcurve */ struct bGPDcurve *BKE_gpencil_stroke_curve_duplicate(struct bGPDcurve *gpc_src); +/** + * Make a copy of a given grease-pencil stroke. + * \param gps_src: Source grease pencil strokes. + * \param dup_points: Duplicate points data. + * \param dup_curve: Duplicate curve data. + * \return Pointer to new stroke. + */ struct bGPDstroke *BKE_gpencil_stroke_duplicate(struct bGPDstroke *gps_src, - const bool dup_points, - const bool dup_curve); + bool dup_points, + bool dup_curve); +/** + * Make a copy of a given gpencil data-block. + * + * XXX: Should this be deprecated? + */ struct bGPdata *BKE_gpencil_data_duplicate(struct Main *bmain, const struct bGPdata *gpd, bool internal_copy); +/** + * Delete the last stroke of the given frame. + * \param gpl: Grease pencil layer + * \param gpf: Grease pencil frame + */ void BKE_gpencil_frame_delete_laststroke(struct bGPDlayer *gpl, struct bGPDframe *gpf); /* materials */ +/** + * Reassign strokes using a material. + * \param gpd: Grease pencil data-block + * \param totcol: Total materials + * \param index: Index of the material + */ void BKE_gpencil_material_index_reassign(struct bGPdata *gpd, int totcol, int index); +/** + * Remove strokes using a material. + * \param gpd: Grease pencil data-block + * \param index: Index of the material + * \return True if removed + */ bool BKE_gpencil_material_index_used(struct bGPdata *gpd, int index); +/** + * Remap material + * \param gpd: Grease pencil data-block + * \param remap: Remap index + * \param remap_len: Remap length + */ void BKE_gpencil_material_remap(struct bGPdata *gpd, const unsigned int *remap, unsigned int remap_len); +/** + * Load a table with material conversion index for merged materials. + * \param ob: Grease pencil object. + * \param hue_threshold: Threshold for Hue. + * \param sat_threshold: Threshold for Saturation. + * \param val_threshold: Threshold for Value. + * \param r_mat_table: return material table. + * \return True if done. + */ bool BKE_gpencil_merge_materials_table_get(struct Object *ob, - const float hue_threshold, - const float sat_threshold, - const float val_threshold, + float hue_threshold, + float sat_threshold, + float val_threshold, struct GHash *r_mat_table); +/** + * Merge similar materials + * \param ob: Grease pencil object + * \param hue_threshold: Threshold for Hue + * \param sat_threshold: Threshold for Saturation + * \param val_threshold: Threshold for Value + * \param r_removed: Number of materials removed + * \return True if done + */ bool BKE_gpencil_merge_materials(struct Object *ob, - const float hue_threshold, - const float sat_threshold, - const float val_threshold, + float hue_threshold, + float sat_threshold, + float val_threshold, int *r_removed); /* statistics functions */ +/** + * Calc grease pencil statistics functions. + * \param gpd: Grease pencil data-block + */ void BKE_gpencil_stats_update(struct bGPdata *gpd); +/** + * Create a new stroke, with pre-allocated data buffers. + * \param mat_idx: Index of the material + * \param totpoints: Total points + * \param thickness: Stroke thickness + * \return Pointer to new stroke + */ struct bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness); +/** + * Create a new stroke and add to frame. + * \param gpf: Grease pencil frame + * \param mat_idx: Material index + * \param totpoints: Total points + * \param thickness: Stroke thickness + * \param insert_at_head: Add to the head of the strokes list + * \return Pointer to new stroke + */ struct bGPDstroke *BKE_gpencil_stroke_add( - struct bGPDframe *gpf, int mat_idx, int totpoints, short thickness, const bool insert_at_head); - + struct bGPDframe *gpf, int mat_idx, int totpoints, short thickness, bool insert_at_head); + +/** + * Add a stroke and copy the temporary drawing color value + * from one of the existing stroke. + * \param gpf: Grease pencil frame + * \param existing: Stroke with the style to copy + * \param mat_idx: Material index + * \param totpoints: Total points + * \param thickness: Stroke thickness + * \return Pointer to new stroke + */ struct bGPDstroke *BKE_gpencil_stroke_add_existing_style(struct bGPDframe *gpf, struct bGPDstroke *existing, int mat_idx, int totpoints, short thickness); -struct bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points); +struct bGPDcurve *BKE_gpencil_stroke_editcurve_new(int tot_curve_points); /* Stroke and Fill - Alpha Visibility Threshold */ #define GPENCIL_ALPHA_OPACITY_THRESH 0.001f #define GPENCIL_STRENGTH_MIN 0.003f +/** + * Check if the given layer is able to be edited or not. + * \param gpl: Grease pencil layer + * \return True if layer is editable + */ bool BKE_gpencil_layer_is_editable(const struct bGPDlayer *gpl); /* How gpencil_layer_getframe() should behave when there @@ -185,28 +339,121 @@ typedef enum eGP_GetFrame_Mode { GP_GETFRAME_ADD_COPY = 2, } eGP_GetFrame_Mode; +/** + * Get the appropriate gp-frame from a given layer + * - this sets the layer's actframe var (if allowed to) + * - extension beyond range (if first gp-frame is after all frame in interest and cannot add) + * + * \param gpl: Grease pencil layer + * \param cframe: Frame number + * \param addnew: Add option + * \return Pointer to new frame + */ struct bGPDframe *BKE_gpencil_layer_frame_get(struct bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew); +/** + * Look up the gp-frame on the requested frame number, but don't add a new one. + * \param gpl: Grease pencil layer + * \param cframe: Frame number + * \return Pointer to frame + */ struct bGPDframe *BKE_gpencil_layer_frame_find(struct bGPDlayer *gpl, int cframe); +/** + * Delete the given frame from a layer. + * \param gpl: Grease pencil layer + * \param gpf: Grease pencil frame + * \return True if delete was done + */ bool BKE_gpencil_layer_frame_delete(struct bGPDlayer *gpl, struct bGPDframe *gpf); +/** + * Get layer by name + * \param gpd: Grease pencil data-block + * \param name: Layer name + * \return Pointer to layer + */ struct bGPDlayer *BKE_gpencil_layer_named_get(struct bGPdata *gpd, const char *name); +/** + * Get the active grease pencil layer for editing. + * \param gpd: Grease pencil data-block + * \return Pointer to layer + */ struct bGPDlayer *BKE_gpencil_layer_active_get(struct bGPdata *gpd); +/** + * Set active grease pencil layer. + * \param gpd: Grease pencil data-block + * \param active: Grease pencil layer to set as active + */ void BKE_gpencil_layer_active_set(struct bGPdata *gpd, struct bGPDlayer *active); +/** + * Delete grease pencil layer. + * \param gpd: Grease pencil data-block + * \param gpl: Grease pencil layer + */ void BKE_gpencil_layer_delete(struct bGPdata *gpd, struct bGPDlayer *gpl); -void BKE_gpencil_layer_autolock_set(struct bGPdata *gpd, const bool unlock); +/** + * Set locked layers for autolock mode. + * \param gpd: Grease pencil data-block + * \param unlock: Unlock flag + */ +void BKE_gpencil_layer_autolock_set(struct bGPdata *gpd, bool unlock); +/** + * Add grease pencil mask layer. + * \param gpl: Grease pencil layer + * \param name: Name of the mask + * \return Pointer to new mask layer + */ struct bGPDlayer_Mask *BKE_gpencil_layer_mask_add(struct bGPDlayer *gpl, const char *name); +/** + * Remove grease pencil mask layer. + * \param gpl: Grease pencil layer + * \param mask: Grease pencil mask layer + */ void BKE_gpencil_layer_mask_remove(struct bGPDlayer *gpl, struct bGPDlayer_Mask *mask); +/** + * Remove any reference to mask layer. + * \param gpd: Grease pencil data-block + * \param name: Name of the mask layer + */ void BKE_gpencil_layer_mask_remove_ref(struct bGPdata *gpd, const char *name); +/** + * Get mask layer by name. + * \param gpl: Grease pencil layer + * \param name: Mask name + * \return Pointer to mask layer + */ struct bGPDlayer_Mask *BKE_gpencil_layer_mask_named_get(struct bGPDlayer *gpl, const char *name); +/** + * Sort grease pencil mask layers. + * \param gpd: Grease pencil data-block + * \param gpl: Grease pencil layer + */ void BKE_gpencil_layer_mask_sort(struct bGPdata *gpd, struct bGPDlayer *gpl); +/** + * Sort all grease pencil mask layer. + * \param gpd: Grease pencil data-block + */ void BKE_gpencil_layer_mask_sort_all(struct bGPdata *gpd); +/** + * Make a copy of a given gpencil mask layers. + */ void BKE_gpencil_layer_mask_copy(const struct bGPDlayer *gpl_src, struct bGPDlayer *gpl_dst); +/** + * Clean any invalid mask layer. + */ void BKE_gpencil_layer_mask_cleanup(struct bGPdata *gpd, struct bGPDlayer *gpl); +/** + * Clean any invalid mask layer for all layers. + */ void BKE_gpencil_layer_mask_cleanup_all_layers(struct bGPdata *gpd); +/** + * Sort grease pencil frames. + * \param gpl: Grease pencil layer + * \param r_has_duplicate_frames: Duplicated frames flag + */ void BKE_gpencil_layer_frames_sort(struct bGPDlayer *gpl, bool *r_has_duplicate_frames); struct bGPDlayer *BKE_gpencil_layer_get_by_name(struct bGPdata *gpd, @@ -214,14 +461,43 @@ struct bGPDlayer *BKE_gpencil_layer_get_by_name(struct bGPdata *gpd, int first_if_not_found); /* Brush */ +/** + * Get grease pencil material from brush. + * \param brush: Brush + * \return Pointer to material + */ struct Material *BKE_gpencil_brush_material_get(struct Brush *brush); +/** + * Set grease pencil brush material. + * \param brush: Brush + * \param material: Material + */ void BKE_gpencil_brush_material_set(struct Brush *brush, struct Material *material); /* Object */ +/** + * Get active color, and add all default settings if we don't find anything. + * \param ob: Grease pencil object + * \return Material pointer + */ struct Material *BKE_gpencil_object_material_ensure_active(struct Object *ob); +/** + * Adds the pinned material to the object if necessary. + * \param bmain: Main pointer + * \param ob: Grease pencil object + * \param brush: Brush + * \return Pointer to material + */ struct Material *BKE_gpencil_object_material_ensure_from_brush(struct Main *bmain, struct Object *ob, struct Brush *brush); +/** + * Assigns the material to object (if not already present) and returns its index (mat_nr). + * \param bmain: Main pointer + * \param ob: Grease pencil object + * \param material: Material + * \return Index of the material + */ int BKE_gpencil_object_material_ensure(struct Main *bmain, struct Object *ob, struct Material *material); @@ -230,49 +506,147 @@ struct Material *BKE_gpencil_object_material_ensure_by_name(struct Main *bmain, const char *name, int *r_index); +/** + * Creates a new grease-pencil material and assigns it to object. + * \param bmain: Main pointer + * \param ob: Grease pencil object + * \param name: Material name + * \param r_index: value is set to zero based index of the new material if \a r_index is not NULL. + * \return Material pointer. + */ struct Material *BKE_gpencil_object_material_new(struct Main *bmain, struct Object *ob, const char *name, int *r_index); +/** + * Get material index (0-based like mat_nr not actcol). + * \param ob: Grease pencil object + * \param ma: Material + * \return Index of the material + */ int BKE_gpencil_object_material_index_get(struct Object *ob, struct Material *ma); int BKE_gpencil_object_material_index_get_by_name(struct Object *ob, const char *name); +/** + * Returns the material for a brush with respect to its pinned state. + * \param ob: Grease pencil object + * \param brush: Brush + * \return Material pointer + */ struct Material *BKE_gpencil_object_material_from_brush_get(struct Object *ob, struct Brush *brush); +/** + * Returns the material index for a brush with respect to its pinned state. + * \param ob: Grease pencil object + * \param brush: Brush + * \return Material index. + */ int BKE_gpencil_object_material_get_index_from_brush(struct Object *ob, struct Brush *brush); +/** + * Guaranteed to return a material assigned to object. Returns never NULL. + * \param bmain: Main pointer + * \param ob: Grease pencil object + * \return Material pointer. + */ struct Material *BKE_gpencil_object_material_ensure_from_active_input_toolsettings( struct Main *bmain, struct Object *ob, struct ToolSettings *ts); +/** + * Guaranteed to return a material assigned to object. Returns never NULL. + * \param bmain: Main pointer + * \param ob: Grease pencil object. + * \param brush: Brush + * \return Material pointer + */ struct Material *BKE_gpencil_object_material_ensure_from_active_input_brush(struct Main *bmain, struct Object *ob, struct Brush *brush); +/** + * Guaranteed to return a material assigned to object. Returns never NULL. + * Only use this for materials unrelated to user input. + * \param ob: Grease pencil object + * \return Material pointer + */ struct Material *BKE_gpencil_object_material_ensure_from_active_input_material(struct Object *ob); +/** + * Check if stroke has any point selected + * \param gps: Grease pencil stroke + * \return True if selected + */ bool BKE_gpencil_stroke_select_check(const struct bGPDstroke *gps); /* vertex groups */ +/** + * Ensure stroke has vertex group. + * \param gps: Grease pencil stroke + */ void BKE_gpencil_dvert_ensure(struct bGPDstroke *gps); +/** + * Remove a vertex group. + * \param ob: Grease pencil object + * \param defgroup: deform group + */ void BKE_gpencil_vgroup_remove(struct Object *ob, struct bDeformGroup *defgroup); +/** + * Make a copy of a given gpencil weights. + * \param gps_src: Source grease pencil stroke + * \param gps_dst: Destination grease pencil stroke + */ void BKE_gpencil_stroke_weights_duplicate(struct bGPDstroke *gps_src, struct bGPDstroke *gps_dst); /* Set active frame by layer. */ +/** + * Set current grease pencil active frame. + * \param depsgraph: Current depsgraph + * \param gpd: Grease pencil data-block. + */ void BKE_gpencil_frame_active_set(struct Depsgraph *depsgraph, struct bGPdata *gpd); +/** + * Get range of selected frames in layer. + * Always the active frame is considered as selected, so if no more selected the range + * will be equal to the current active frame. + * \param gpl: Layer. + * \param r_initframe: Number of first selected frame. + * \param r_endframe: Number of last selected frame. + */ void BKE_gpencil_frame_range_selected(struct bGPDlayer *gpl, int *r_initframe, int *r_endframe); +/** + * Get Falloff factor base on frame range + * \param gpf: Frame. + * \param actnum: Number of active frame in layer. + * \param f_init: Number of first selected frame. + * \param f_end: Number of last selected frame. + * \param cur_falloff: Curve with falloff factors. + */ float BKE_gpencil_multiframe_falloff_calc( struct bGPDframe *gpf, int actnum, int f_init, int f_end, struct CurveMapping *cur_falloff); +/** + * Create a default palette. + * \param bmain: Main pointer + * \param scene: Scene + */ void BKE_gpencil_palette_ensure(struct Main *bmain, struct Scene *scene); -bool BKE_gpencil_from_image(struct SpaceImage *sima, - struct bGPdata *gpd, - struct bGPDframe *gpf, - const float size, - const bool mask); +/** + * Create grease pencil strokes from image + * \param sima: Image + * \param gpd: Grease pencil data-block + * \param gpf: Grease pencil frame + * \param size: Size + * \param mask: Mask + * \return True if done + */ +bool BKE_gpencil_from_image( + struct SpaceImage *sima, struct bGPdata *gpd, struct bGPDframe *gpf, float size, bool mask); /* Iterators */ -/* frame & stroke are NULL if it is a layer callback. */ +/** + * Frame & stroke are NULL if it is a layer callback. + */ typedef void (*gpIterCb)(struct bGPDlayer *layer, struct bGPDframe *frame, struct bGPDstroke *stroke, @@ -294,17 +668,45 @@ void BKE_gpencil_visible_stroke_advanced_iter(struct ViewLayer *view_layer, extern void (*BKE_gpencil_batch_cache_dirty_tag_cb)(struct bGPdata *gpd); extern void (*BKE_gpencil_batch_cache_free_cb)(struct bGPdata *gpd); +/** + * Update original pointers in evaluated frame. + * \param gpf_orig: Original grease-pencil frame. + * \param gpf_eval: Evaluated grease pencil frame. + */ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig, const struct bGPDframe *gpf_eval); +/** + * Update pointers of eval data to original data to keep references. + * \param ob_orig: Original grease pencil object + * \param ob_eval: Evaluated grease pencil object + */ void BKE_gpencil_update_orig_pointers(const struct Object *ob_orig, const struct Object *ob_eval); +/** + * Get parent matrix, including layer parenting. + * \param depsgraph: Depsgraph + * \param obact: Grease pencil object + * \param gpl: Grease pencil layer + * \param diff_mat: Result parent matrix + */ void BKE_gpencil_layer_transform_matrix_get(const struct Depsgraph *depsgraph, struct Object *obact, struct bGPDlayer *gpl, float diff_mat[4][4]); +/** + * Update parent matrix and local transforms. + * \param depsgraph: Depsgraph + * \param ob: Grease pencil object + */ void BKE_gpencil_update_layer_transforms(const struct Depsgraph *depsgraph, struct Object *ob); +/** + * Find material by name prefix. + * \param ob: Object pointer + * \param name_prefix: Prefix name of the material + * \return Index + */ int BKE_gpencil_material_find_index_by_name_prefix(struct Object *ob, const char *name_prefix); void BKE_gpencil_blend_read_data(struct BlendDataReader *reader, struct bGPdata *gpd); diff --git a/source/blender/blenkernel/BKE_gpencil_curve.h b/source/blender/blenkernel/BKE_gpencil_curve.h index 9cbe67af9c1..5c5f96c17f1 100644 --- a/source/blender/blenkernel/BKE_gpencil_curve.h +++ b/source/blender/blenkernel/BKE_gpencil_curve.h @@ -35,34 +35,63 @@ struct bGPDlayer; struct bGPDstroke; struct bGPdata; +/** + * Convert a curve object to grease pencil stroke. + * + * \param bmain: Main thread pointer + * \param scene: Original scene. + * \param ob_gp: Grease pencil object to add strokes. + * \param ob_cu: Curve to convert. + * \param use_collections: Create layers using collection names. + * \param scale_thickness: Scale thickness factor. + * \param sample: Sample distance, zero to disable. + */ void BKE_gpencil_convert_curve(struct Main *bmain, struct Scene *scene, struct Object *ob_gp, struct Object *ob_cu, - const bool use_collections, - const float scale_thickness, - const float sample); + bool use_collections, + float scale_thickness, + float sample); +/** + * Creates a bGPDcurve by doing a cubic curve fitting on the grease pencil stroke points. + */ struct bGPDcurve *BKE_gpencil_stroke_editcurve_generate(struct bGPDstroke *gps, - const float error_threshold, - const float corner_angle, - const float stroke_radius); + float error_threshold, + float corner_angle, + float stroke_radius); +/** + * Updates the edit-curve for a stroke. Frees the old curve if one exists and generates a new one. + */ void BKE_gpencil_stroke_editcurve_update(struct bGPdata *gpd, struct bGPDlayer *gpl, struct bGPDstroke *gps); +/** + * Sync the selection from stroke to edit-curve. + */ void BKE_gpencil_editcurve_stroke_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps, struct bGPDcurve *gpc); +/** + * Sync the selection from edit-curve to stroke. + */ void BKE_gpencil_stroke_editcurve_sync_selection(struct bGPdata *gpd, struct bGPDstroke *gps, struct bGPDcurve *gpc); void BKE_gpencil_strokes_selected_update_editcurve(struct bGPdata *gpd); void BKE_gpencil_strokes_selected_sync_selection_editcurve(struct bGPdata *gpd); +/** + * Recalculate stroke points with the edit-curve of the stroke. + */ void BKE_gpencil_stroke_update_geometry_from_editcurve(struct bGPDstroke *gps, - const uint resolution, - const bool is_adaptive); + uint resolution, + bool is_adaptive); +/** + * Recalculate the handles of the edit curve of a grease pencil stroke. + */ void BKE_gpencil_editcurve_recalculate_handles(struct bGPDstroke *gps); -void BKE_gpencil_editcurve_subdivide(struct bGPDstroke *gps, const int cuts); +void BKE_gpencil_editcurve_subdivide(struct bGPDstroke *gps, int cuts); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index a9cd553a8fe..24b820b06cc 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -38,49 +38,159 @@ struct bGPDspoint; struct bGPDstroke; struct bGPdata; -/* Object boundbox. */ +/* Object bound-box. */ + +/** + * Get min/max bounds of all strokes in grease pencil data-block. + * \param gpd: Grease pencil data-block + * \param r_min: Result minimum coordinates + * \param r_max: Result maximum coordinates + * \return True if it was possible to calculate + */ bool BKE_gpencil_data_minmax(const struct bGPdata *gpd, float r_min[3], float r_max[3]); +/** + * Get min/max coordinate bounds for single stroke. + * \param gps: Grease pencil stroke + * \param use_select: Include only selected points + * \param r_min: Result minimum coordinates + * \param r_max: Result maximum coordinates + * \return True if it was possible to calculate + */ bool BKE_gpencil_stroke_minmax(const struct bGPDstroke *gps, - const bool use_select, + bool use_select, float r_min[3], float r_max[3]); +/** + * Get grease pencil object bounding box. + * \param ob: Grease pencil object + * \return Bounding box + */ struct BoundBox *BKE_gpencil_boundbox_get(struct Object *ob); +/** + * Compute center of bounding box. + * \param gpd: Grease pencil data-block + * \param r_centroid: Location of the center + */ void BKE_gpencil_centroid_3d(struct bGPdata *gpd, float r_centroid[3]); +/** + * Compute stroke bounding box. + * \param gps: Grease pencil Stroke + */ void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps); -/* stroke geometry utilities */ +/* Stroke geometry utilities. */ + +/** + * Calculate stroke normals. + * \param gps: Grease pencil stroke + * \param r_normal: Return Normal vector normalized + */ void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]); +/** + * Reduce a series of points to a simplified version, + * but maintains the general shape of the series. + * + * Ramer - Douglas - Peucker algorithm + * by http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm + * \param gpd: Grease pencil data-block + * \param gps: Grease pencil stroke + * \param epsilon: Epsilon value to define precision of the algorithm + */ void BKE_gpencil_stroke_simplify_adaptive(struct bGPdata *gpd, struct bGPDstroke *gps, float epsilon); +/** + * Simplify alternate vertex of stroke except extremes. + * \param gpd: Grease pencil data-block + * \param gps: Grease pencil stroke + */ void BKE_gpencil_stroke_simplify_fixed(struct bGPdata *gpd, struct bGPDstroke *gps); +/** + * Subdivide a stroke + * \param gpd: Grease pencil data-block + * \param gps: Stroke + * \param level: Level of subdivision + * \param type: Type of subdivision + */ void BKE_gpencil_stroke_subdivide(struct bGPdata *gpd, struct bGPDstroke *gps, int level, int type); +/** + * Trim stroke to the first intersection or loop. + * \param gps: Stroke data + */ bool BKE_gpencil_stroke_trim(struct bGPdata *gpd, struct bGPDstroke *gps); +/** + * Reduce a series of points when the distance is below a threshold. + * 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. + */ void BKE_gpencil_stroke_merge_distance(struct bGPdata *gpd, struct bGPDframe *gpf, struct bGPDstroke *gps, - const float threshold, - const bool use_unselected); + float threshold, + bool use_unselected); +/** + * Get points of stroke always flat to view not affected + * by camera view or view position. + * \param points: Array of grease pencil points (3D) + * \param totpoints: Total of points + * \param points2d: Result array of 2D points + * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0) + */ void BKE_gpencil_stroke_2d_flat(const struct bGPDspoint *points, int totpoints, float (*points2d)[2], int *r_direction); +/** + * Get points of stroke always flat to view not affected by camera view or view position + * using another stroke as reference. + * \param ref_points: Array of reference points (3D) + * \param ref_totpoints: Total reference points + * \param points: Array of points to flat (3D) + * \param totpoints: Total points + * \param points2d: Result array of 2D points + * \param scale: Scale factor + * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0) + */ void BKE_gpencil_stroke_2d_flat_ref(const struct bGPDspoint *ref_points, int ref_totpoints, const struct bGPDspoint *points, int totpoints, float (*points2d)[2], - const float scale, + float scale, int *r_direction); +/** + * Triangulate stroke to generate data for filling areas. + * \param gps: Grease pencil stroke + */ void BKE_gpencil_stroke_fill_triangulate(struct bGPDstroke *gps); +/** + * Recalc all internal geometry data for the stroke + * \param gpd: Grease pencil data-block + * \param gps: Grease pencil stroke + */ void BKE_gpencil_stroke_geometry_update(struct bGPdata *gpd, struct bGPDstroke *gps); +/** + * Update Stroke UV data. + * \param gps: Grease pencil stroke + */ void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); +/** + * Apply grease pencil Transforms. + * \param gpd: Grease pencil data-block + * \param mat: Transformation matrix + */ void BKE_gpencil_transform(struct bGPdata *gpd, const float mat[4][4]); typedef struct GPencilPointCoordinates { @@ -90,45 +200,118 @@ typedef struct GPencilPointCoordinates { float pressure; } GPencilPointCoordinates; +/** + * \note Used for "move only origins" in object_data_transform.c. + */ int BKE_gpencil_stroke_point_count(const struct bGPdata *gpd); +/** + * \note Used for "move only origins" in object_data_transform.c. + */ void BKE_gpencil_point_coords_get(struct bGPdata *gpd, GPencilPointCoordinates *elem_data); +/** + * \note Used for "move only origins" in object_data_transform.c. + */ void BKE_gpencil_point_coords_apply(struct bGPdata *gpd, const GPencilPointCoordinates *elem_data); +/** + * \note Used for "move only origins" in object_data_transform.c. + */ void BKE_gpencil_point_coords_apply_with_mat4(struct bGPdata *gpd, const GPencilPointCoordinates *elem_data, const float mat[4][4]); +/** + * Resample a stroke + * \param gpd: Grease pencil data-block + * \param gps: Stroke to sample + * \param dist: Distance of one segment + */ bool BKE_gpencil_stroke_sample(struct bGPdata *gpd, struct bGPDstroke *gps, - const float dist, - const bool select); -bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf); + float dist, + bool select); +/** + * Apply smooth position to stroke point. + * \param gps: Stroke to smooth + * \param i: Point index + * \param inf: Amount of smoothing to apply + * \param smooth_caps: Apply smooth to stroke extremes + */ +bool BKE_gpencil_stroke_smooth_point(struct bGPDstroke *gps, int i, float inf, bool smooth_caps); +/** + * Apply smooth strength to stroke point. + * \param gps: Stroke to smooth + * \param point_index: Point index + * \param influence: Amount of smoothing to apply + */ bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); +/** + * Apply smooth for thickness to stroke point (use pressure). + * \param gps: Stroke to smooth + * \param point_index: Point index + * \param influence: Amount of smoothing to apply + */ bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); +/** + * Apply smooth for UV rotation to stroke point (use pressure). + * \param gps: Stroke to smooth + * \param point_index: Point index + * \param influence: Amount of smoothing to apply + */ bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); +/** + * Close grease pencil stroke. + * \param gps: Stroke to close + */ bool BKE_gpencil_stroke_close(struct bGPDstroke *gps); +/** + * Dissolve points in stroke. + * \param gpd: Grease pencil data-block + * \param gpf: Grease pencil frame + * \param gps: Grease pencil stroke + * \param tag: Type of tag for point + */ void BKE_gpencil_dissolve_points(struct bGPdata *gpd, struct bGPDframe *gpf, struct bGPDstroke *gps, - const short tag); + short tag); +/** + * Backbone stretch similar to Freestyle. + * \param gps: Stroke to sample. + * \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(struct bGPDstroke *gps, - const float dist, - const float overshoot_fac, - 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); + float dist, + float overshoot_fac, + short mode, + bool follow_curvature, + int extra_point_count, + float segment_influence, + float max_angle, + bool invert_curvature); +/** + * Trim stroke to needed segments. + * \param gps: Target stroke. + * \param index_from: the index of the first point to be used in the trimmed result. + * \param index_to: the index of the last point to be used in the trimmed result. + */ +bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, int index_from, int index_to); +/** + * Split the given stroke into several new strokes, partitioning + * it based on whether the stroke points have a particular flag + * is set (e.g. #GP_SPOINT_SELECT in most cases, but not always). + */ struct bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(struct bGPdata *gpd, struct bGPDframe *gpf, struct bGPDstroke *gps, struct bGPDstroke *next_stroke, int tag_flags, bool select, + bool flat_cap, int limit); void BKE_gpencil_curve_delete_tagged_points(struct bGPdata *gpd, struct bGPDframe *gpf, @@ -137,65 +320,148 @@ void BKE_gpencil_curve_delete_tagged_points(struct bGPdata *gpd, struct bGPDcurve *gpc, int tag_flags); +/** + * Flip stroke. + */ void BKE_gpencil_stroke_flip(struct bGPDstroke *gps); +/** + * Split stroke. + * \param gpd: Grease pencil data-block. + * \param gpf: Grease pencil frame. + * \param gps: Grease pencil original stroke. + * \param before_index: Position of the point to split. + * \param remaining_gps: Secondary stroke after split. + * \return True if the split was done + */ bool BKE_gpencil_stroke_split(struct bGPdata *gpd, struct bGPDframe *gpf, struct bGPDstroke *gps, - const int before_index, + int before_index, struct bGPDstroke **remaining_gps); -bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist, const short mode); +/** + * Shrink the stroke by length. + * \param gps: Stroke to shrink + * \param dist: delta length + * \param mode: 1->Start, 2->End + */ +bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, float dist, short mode); +/** + * Calculate grease pencil stroke length. + * \param gps: Grease pencil stroke. + * \param use_3d: Set to true to use 3D points. + * \return Length of the stroke. + */ float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d); +/** Calculate grease pencil stroke length between points. */ float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps, - const int start_index, - const int end_index, + int start_index, + int end_index, bool use_3d); +/** + * Set a random color to stroke using vertex color. + * \param gps: Stroke + */ void BKE_gpencil_stroke_set_random_color(struct bGPDstroke *gps); +/** + * Join two strokes using the shortest distance (reorder stroke if necessary). + */ void BKE_gpencil_stroke_join(struct bGPDstroke *gps_a, struct bGPDstroke *gps_b, - const bool leave_gaps, - const bool fit_thickness, - const bool smooth); + bool leave_gaps, + bool fit_thickness, + bool smooth); +/** + * Copy the stroke of the frame to all frames selected (except current). + */ void BKE_gpencil_stroke_copy_to_keyframes(struct bGPdata *gpd, struct bGPDlayer *gpl, struct bGPDframe *gpf, struct bGPDstroke *gps, - const bool tail); + bool tail); +/** + * Convert a mesh object to grease pencil stroke. + * + * \param bmain: Main thread pointer. + * \param depsgraph: Original depsgraph. + * \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 edge-loop ends. + * \param thickness: Thickness of the strokes. + * \param offset: Offset along the normals. + * \param matrix: Transformation matrix. + * \param frame_offset: Destination frame number offset. + * \param use_seams: Only export seam edges. + * \param use_faces: Export faces as filled strokes. + */ bool BKE_gpencil_convert_mesh(struct Main *bmain, struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob_gp, struct Object *ob_mesh, - const float angle, - const int thickness, - const float offset, + float angle, + int thickness, + float offset, const float matrix[4][4], - const int frame_offset, - const bool use_seams, - const bool use_faces, - const bool use_vgroups); + int frame_offset, + bool use_seams, + bool use_faces, + bool use_vgroups); +/** + * Subdivide the grease pencil stroke so the number of points is target_number. + * Does not change the shape of the stroke. The new points will be distributed as + * uniformly as possible by repeatedly subdividing the current longest edge. + * + * \param gps: The stroke to be up-sampled. + * \param target_number: The number of points the up-sampled stroke should have. + * \param select: Select/Deselect the stroke. + */ void BKE_gpencil_stroke_uniform_subdivide(struct bGPdata *gpd, struct bGPDstroke *gps, - const uint32_t target_number, - const bool select); + uint32_t target_number, + bool select); +/** + * 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. + */ void BKE_gpencil_stroke_to_view_space(struct RegionView3D *rv3d, struct bGPDstroke *gps, const float diff_mat[4][4]); +/** + * 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. + */ void BKE_gpencil_stroke_from_view_space(struct RegionView3D *rv3d, struct bGPDstroke *gps, const float diff_mat[4][4]); +/** + * Calculates the perimeter of a stroke projected from the view and returns it as a new stroke. + * \param subdivisions: Number of subdivisions for the start and end caps. + * \return: bGPDstroke pointer to stroke perimeter. + */ struct bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d, struct bGPdata *gpd, const struct bGPDlayer *gpl, struct bGPDstroke *gps, - const int subdivisions, + int subdivisions, const float diff_mat[4][4]); +/** + * Get average pressure. + */ float BKE_gpencil_stroke_average_pressure_get(struct bGPDstroke *gps); +/** + * Check if the thickness of the stroke is constant. + */ bool BKE_gpencil_stroke_is_pressure_constant(struct bGPDstroke *gps); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_gpencil_modifier.h b/source/blender/blenkernel/BKE_gpencil_modifier.h index 33524e47473..6df13df1c01 100644 --- a/source/blender/blenkernel/BKE_gpencil_modifier.h +++ b/source/blender/blenkernel/BKE_gpencil_modifier.h @@ -207,7 +207,7 @@ typedef struct GpencilModifierTypeInfo { */ void (*updateDepsgraph)(struct GpencilModifierData *md, const struct ModifierUpdateDepsgraphContext *ctx, - const int mode); + int mode); /** * Should return true if the modifier needs to be recalculated on time @@ -249,36 +249,114 @@ typedef struct GpencilModifierTypeInfo { #define GPENCIL_MODIFIER_TYPE_PANEL_PREFIX "MOD_PT_gpencil_" -/* Initialize modifier's global data (type info and some common global storage). */ +/** + * Initialize modifier's global data (type info and some common global storage). + */ void BKE_gpencil_modifier_init(void); +/** + * Get the idname of the modifier type's panel, which was defined in the #panelRegister callback. + * + * \param type: Type of modifier. + * \param r_idname: ID name. + */ void BKE_gpencil_modifierType_panel_id(GpencilModifierType type, char *r_idname); void BKE_gpencil_modifier_panel_expand(struct GpencilModifierData *md); +/** + * Get grease pencil modifier information. + * \param type: Type of modifier. + * \return Pointer to type + */ const GpencilModifierTypeInfo *BKE_gpencil_modifier_get_info(GpencilModifierType type); +/** + * Create new grease pencil modifier. + * \param type: Type of modifier. + * \return New modifier pointer. + */ struct GpencilModifierData *BKE_gpencil_modifier_new(int type); -void BKE_gpencil_modifier_free_ex(struct GpencilModifierData *md, const int flag); +/** + * Free grease pencil modifier data + * \param md: Modifier data. + * \param flag: Flags. + */ +void BKE_gpencil_modifier_free_ex(struct GpencilModifierData *md, int flag); +/** + * Free grease pencil modifier data + * \param md: Modifier data. + */ void BKE_gpencil_modifier_free(struct GpencilModifierData *md); +/* check unique name */ bool BKE_gpencil_modifier_unique_name(struct ListBase *modifiers, struct GpencilModifierData *gmd); +/** + * Check if grease pencil modifier depends on time. + * \param md: Modifier data. + * \return True if depends on time. + */ bool BKE_gpencil_modifier_depends_ontime(struct GpencilModifierData *md); struct GpencilModifierData *BKE_gpencil_modifiers_findby_type(struct Object *ob, GpencilModifierType type); +/** + * Find grease pencil modifier by name. + * \param ob: Grease pencil object. + * \param name: Name to find. + * \return Pointer to modifier. + */ struct GpencilModifierData *BKE_gpencil_modifiers_findby_name(struct Object *ob, const char *name); +/** + * Generic grease pencil modifier copy data. + * \param md_src: Source modifier data. + * \param md_dst: Target modifier data. + */ void BKE_gpencil_modifier_copydata_generic(const struct GpencilModifierData *md_src, struct GpencilModifierData *md_dst); +/** + * Copy grease pencil modifier data. + * \param md: Source modifier data. + * \param target: Target modifier data. + */ void BKE_gpencil_modifier_copydata(struct GpencilModifierData *md, struct GpencilModifierData *target); +/** + * Copy grease pencil modifier data. + * \param md: Source modifier data. + * \param target: Target modifier data. + * \param flag: Flags. + */ void BKE_gpencil_modifier_copydata_ex(struct GpencilModifierData *md, struct GpencilModifierData *target, - const int flag); + int flag); +/** + * Set grease pencil modifier error. + * \param md: Modifier data. + * \param format: Format. + */ void BKE_gpencil_modifier_set_error(struct GpencilModifierData *md, const char *format, ...) ATTR_PRINTF_FORMAT(2, 3); +/** + * Link grease pencil modifier related IDs. + * \param ob: Grease pencil object. + * \param walk: Walk option. + * \param userData: User data. + */ void BKE_gpencil_modifiers_foreach_ID_link(struct Object *ob, GreasePencilIDWalkFunc walk, void *userData); +/** + * Link grease pencil modifier related Texts. + * \param ob: Grease pencil object. + * \param walk: Walk option. + * \param userData: User data. + */ void BKE_gpencil_modifiers_foreach_tex_link(struct Object *ob, GreasePencilTexWalkFunc walk, void *userData); +/** + * Check whether given modifier is not local (i.e. from linked data) when the object is a library + * override. + * + * \param gmd: May be NULL, in which case we consider it as a non-local modifier case. + */ bool BKE_gpencil_modifier_is_nonlocal_in_liboverride(const struct Object *ob, const struct GpencilModifierData *gmd); @@ -287,11 +365,30 @@ typedef struct GpencilVirtualModifierData { LatticeGpencilModifierData lmd; } GpencilVirtualModifierData; +/** + * This is to include things that are not modifiers in the evaluation of the modifier stack, + * for example parenting to an armature or lattice without having a real modifier. + */ struct GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist( const struct Object *ob, struct GpencilVirtualModifierData *data); +/** + * Check if object has grease pencil Geometry modifiers. + * \param ob: Grease pencil object. + * \return True if exist. + */ bool BKE_gpencil_has_geometry_modifiers(struct Object *ob); +/** + * Check if object has grease pencil Time modifiers. + * \param ob: Grease pencil object. + * \return True if exist. + */ bool BKE_gpencil_has_time_modifiers(struct Object *ob); +/** + * Check if object has grease pencil transform stroke modifiers. + * \param ob: Grease pencil object. + * \return True if exist. + */ bool BKE_gpencil_has_transform_modifiers(struct Object *ob); /* Stores the maximum calculation range in the whole modifier stack for line art so the cache can @@ -306,31 +403,62 @@ GpencilLineartLimitInfo BKE_gpencil_get_lineart_modifier_limits(const struct Obj void BKE_gpencil_set_lineart_modifier_limits(struct GpencilModifierData *md, const struct GpencilLineartLimitInfo *info, - const bool is_first_lineart); + bool is_first_lineart); bool BKE_gpencil_is_first_lineart_in_stack(const struct Object *ob, const struct GpencilModifierData *md); -void BKE_gpencil_lattice_init(struct Object *ob); -void BKE_gpencil_lattice_clear(struct Object *ob); +/** + * Init grease pencil cache deform data. + * \param ob: Grease pencil object + */ +void BKE_gpencil_cache_data_init(struct Depsgraph *depsgraph, struct Object *ob); +/** + * Clear grease pencil cache deform data. + * \param ob: Grease pencil object + */ +void BKE_gpencil_cache_data_clear(struct Object *ob); +/** + * Calculate grease-pencil modifiers. + * \param depsgraph: Current depsgraph. + * \param scene: Current scene. + * \param ob: Grease pencil object. + */ void BKE_gpencil_modifiers_calc(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * Prepare grease pencil eval data for modifiers + * \param depsgraph: Current depsgraph. + * \param scene: Current scene. + * \param ob: Grease pencil object. + */ void BKE_gpencil_prepare_eval_data(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * Get the current frame re-timed with time modifiers. + * \param depsgraph: Current depsgraph. + * \param scene: Current scene. + * \param ob: Grease pencil object. + * \param gpl: Grease pencil layer. + * \return New frame number. + */ struct bGPDframe *BKE_gpencil_frame_retime_get(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct bGPDlayer *gpl); +/** + * Get Time modifier frame number. + */ 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); + int cfra, + 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_icons.h b/source/blender/blenkernel/BKE_icons.h index 28a6f837f61..a65cdcd23af 100644 --- a/source/blender/blenkernel/BKE_icons.h +++ b/source/blender/blenkernel/BKE_icons.h @@ -97,88 +97,154 @@ enum eIconSizes; void BKE_icons_init(int first_dyn_id); -/* return icon id for library object or create new icon if not found */ +/** + * Return icon id for library object or create new icon if not found. + */ int BKE_icon_id_ensure(struct ID *id); -/* return icon id for Grease Pencil layer (color preview) or create new icon if not found */ +/** + * Return icon id for Grease Pencil layer (color preview) or create new icon if not found. + */ int BKE_icon_gplayer_color_ensure(struct bGPDlayer *gpl); +/** + * Return icon id of given preview, or create new icon if not found. + */ int BKE_icon_preview_ensure(struct ID *id, struct PreviewImage *preview); +/** + * Create an icon as owner or \a ibuf. The icon-ID is not stored in \a ibuf, + * it needs to be stored separately. + * \note Transforms ownership of \a ibuf to the newly created icon. + */ int BKE_icon_imbuf_create(struct ImBuf *ibuf) ATTR_WARN_UNUSED_RESULT; struct ImBuf *BKE_icon_imbuf_get_buffer(int icon_id) ATTR_WARN_UNUSED_RESULT; -/* retrieve icon for id */ -struct Icon *BKE_icon_get(const int icon_id); +/** + * Retrieve icon for id. + */ +struct Icon *BKE_icon_get(int icon_id); -/* set icon for id if not already defined */ -/* used for inserting the internal icons */ -void BKE_icon_set(const int icon_id, struct Icon *icon); +/** + * Set icon for id if not already defined. + * Used for inserting the internal icons. + */ +void BKE_icon_set(int icon_id, struct Icon *icon); -/* remove icon and free data if library object becomes invalid */ +/** + * Remove icon and free data if library object becomes invalid. + */ void BKE_icon_id_delete(struct ID *id); -bool BKE_icon_delete(const int icon_id); -bool BKE_icon_delete_unmanaged(const int icon_id); +/** + * Remove icon and free data. + */ +bool BKE_icon_delete(int icon_id); +bool BKE_icon_delete_unmanaged(int icon_id); -/* report changes - icon needs to be recalculated */ -void BKE_icon_changed(const int icon_id); +/** + * Report changes - icon needs to be recalculated. + */ +void BKE_icon_changed(int icon_id); -/* free all icons */ +/** + * Free all icons. + */ void BKE_icons_free(void); -/* free all icons marked for deferred deletion */ +/** + * Free all icons marked for deferred deletion. + */ void BKE_icons_deferred_free(void); -/* free the preview image for use in list */ +/** + * Free the preview image for use in list. + */ void BKE_previewimg_freefunc(void *link); -/* free the preview image */ +/** + * Free the preview image. + */ void BKE_previewimg_free(struct PreviewImage **prv); -/* clear the preview image or icon, but does not free it */ +/** + * Clear the preview image or icon, but does not free it. + */ void BKE_previewimg_clear(struct PreviewImage *prv); -/* clear the preview image or icon at a specific size */ +/** + * Clear the preview image or icon at a specific size. + */ void BKE_previewimg_clear_single(struct PreviewImage *prv, enum eIconSizes size); -/* get the preview from any pointer */ +/** + * Get the preview from any pointer. + */ struct PreviewImage **BKE_previewimg_id_get_p(const struct ID *id); struct PreviewImage *BKE_previewimg_id_get(const struct ID *id); bool BKE_previewimg_id_supports_jobs(const struct ID *id); -/* Trigger deferred loading of a custom image file into the preview buffer. */ +/** + * Trigger deferred loading of a custom image file into the preview buffer. + */ void BKE_previewimg_id_custom_set(struct ID *id, const char *path); -/* free the preview image belonging to the id */ +/** + * Free the preview image belonging to the id. + */ void BKE_previewimg_id_free(struct ID *id); -/* create a new preview image */ +/** + * Create a new preview image. + */ struct PreviewImage *BKE_previewimg_create(void); -/* create a copy of the preview image */ +/** + * Create a copy of the preview image. + */ struct PreviewImage *BKE_previewimg_copy(const struct PreviewImage *prv); +/** + * Duplicate preview image from \a id and clear icon_id, + * to be used by data-block copy functions. + */ void BKE_previewimg_id_copy(struct ID *new_id, const struct ID *old_id); -/* retrieve existing or create new preview image */ +/** + * Retrieve existing or create new preview image. + */ struct PreviewImage *BKE_previewimg_id_ensure(struct ID *id); -void BKE_previewimg_ensure(struct PreviewImage *prv, const int size); +/** + * Handle deferred (lazy) loading/generation of preview image, if needed. + * For now, only used with file thumbnails. + */ +void BKE_previewimg_ensure(struct PreviewImage *prv, int size); -struct ImBuf *BKE_previewimg_to_imbuf(struct PreviewImage *prv, const int size); +/** + * Create an #ImBuf holding a copy of the preview image buffer in \a prv. + * \note The returned image buffer has to be free'd (#IMB_freeImBuf()). + */ +struct ImBuf *BKE_previewimg_to_imbuf(struct PreviewImage *prv, int size); -void BKE_previewimg_finish(struct PreviewImage *prv, const int size); -bool BKE_previewimg_is_finished(const struct PreviewImage *prv, const int size); +void BKE_previewimg_finish(struct PreviewImage *prv, int size); +bool BKE_previewimg_is_finished(const struct PreviewImage *prv, int size); struct PreviewImage *BKE_previewimg_cached_get(const char *name); +/** + * Generate an empty #PreviewImage, if not yet existing. + */ struct PreviewImage *BKE_previewimg_cached_ensure(const char *name); +/** + * Generate a #PreviewImage from given file path, using thumbnails management, if not yet existing. + * Does not actually generate the preview, #BKE_previewimg_ensure() must be called for that. + */ struct PreviewImage *BKE_previewimg_cached_thumbnail_read(const char *name, const char *path, - const int source, + int source, bool force_update); void BKE_previewimg_cached_release(const char *name); @@ -193,8 +259,8 @@ struct Icon_Geom *BKE_icon_geom_from_memory(uchar *data, size_t data_len); struct Icon_Geom *BKE_icon_geom_from_file(const char *filename); struct ImBuf *BKE_icon_geom_rasterize(const struct Icon_Geom *geom, - const unsigned int size_x, - const unsigned int size_y); + unsigned int size_x, + unsigned int size_y); void BKE_icon_geom_invert_lightness(struct Icon_Geom *geom); int BKE_icon_ensure_studio_light(struct StudioLight *sl, int id_type); diff --git a/source/blender/blenkernel/BKE_idprop.h b/source/blender/blenkernel/BKE_idprop.h index c28ac63388b..b0b981e49f0 100644 --- a/source/blender/blenkernel/BKE_idprop.h +++ b/source/blender/blenkernel/BKE_idprop.h @@ -56,11 +56,17 @@ typedef union IDPropertyTemplate { /* ----------- Property Array Type ---------- */ +/** + * \note as a start to move away from the stupid #IDP_New function, + * this type has its own allocation function. + */ struct IDProperty *IDP_NewIDPArray(const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); struct IDProperty *IDP_CopyIDPArray(const struct IDProperty *array, - const int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); + int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -/* shallow copies item */ +/** + * Shallow copies item. + */ void IDP_SetIndexArray(struct IDProperty *prop, int index, struct IDProperty *item) ATTR_NONNULL(); struct IDProperty *IDP_GetIndexArray(struct IDProperty *prop, int index) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); @@ -68,11 +74,20 @@ void IDP_AppendArray(struct IDProperty *prop, struct IDProperty *item); void IDP_ResizeIDPArray(struct IDProperty *prop, int len); /* ----------- Numeric Array Type ----------- */ -/* This function works for strings too! */ + +/** + * This function works for strings too! + */ void IDP_ResizeArray(struct IDProperty *prop, int newlen); void IDP_FreeArray(struct IDProperty *prop); /* ---------- String Type ------------ */ +/** + * \param st: The string to assign. + * \param name: The property name. + * \param maxlen: The size of the new string (including the \0 terminator). + * \return The new string property. + */ struct IDProperty *IDP_NewString(const char *st, const char *name, int maxlen) ATTR_WARN_UNUSED_RESULT @@ -87,65 +102,152 @@ void IDP_FreeString(struct IDProperty *prop) ATTR_NONNULL(); typedef void (*IDPWalkFunc)(void *userData, struct IDProperty *idp); -void IDP_AssignID(struct IDProperty *prop, struct ID *id, const int flag); +void IDP_AssignID(struct IDProperty *prop, struct ID *id, int flag); /*-------- Group Functions -------*/ -/** Sync values from one group to another, only where they match */ +/** + * Sync values from one group to another when values name and types match, + * copy the values, else ignore. + * + * \note Use for syncing proxies. + */ void IDP_SyncGroupValues(struct IDProperty *dest, const struct IDProperty *src) ATTR_NONNULL(); -void IDP_SyncGroupTypes(struct IDProperty *dest, - const struct IDProperty *src, - const bool do_arraylen) ATTR_NONNULL(); +void IDP_SyncGroupTypes(struct IDProperty *dest, const struct IDProperty *src, bool do_arraylen) + ATTR_NONNULL(); +/** + * Replaces all properties with the same name in a destination group from a source group. + */ void IDP_ReplaceGroupInGroup(struct IDProperty *dest, const struct IDProperty *src) ATTR_NONNULL(); void IDP_ReplaceInGroup(struct IDProperty *group, struct IDProperty *prop) ATTR_NONNULL(); +/** + * Checks if a property with the same name as prop exists, and if so replaces it. + * Use this to preserve order! + */ void IDP_ReplaceInGroup_ex(struct IDProperty *group, struct IDProperty *prop, struct IDProperty *prop_exist); -void IDP_MergeGroup(struct IDProperty *dest, const struct IDProperty *src, const bool do_overwrite) +/** + * If a property is missing in \a dest, add it. + * Do it recursively. + */ +void IDP_MergeGroup(struct IDProperty *dest, const struct IDProperty *src, bool do_overwrite) ATTR_NONNULL(); +/** + * If a property is missing in \a dest, add it. + * Do it recursively. + */ void IDP_MergeGroup_ex(struct IDProperty *dest, const struct IDProperty *src, - const bool do_overwrite, - const int flag) ATTR_NONNULL(); + bool do_overwrite, + int flag) ATTR_NONNULL(); +/** + * This function has a sanity check to make sure ID properties with the same name don't + * get added to the group. + * + * The sanity check just means the property is not added to the group if another property + * exists with the same name; the client code using ID properties then needs to detect this + * (the function that adds new properties to groups, #IDP_AddToGroup, + * returns false if a property can't be added to the group, and true if it can) + * and free the property. + */ bool IDP_AddToGroup(struct IDProperty *group, struct IDProperty *prop) ATTR_NONNULL(); +/** + * This is the same as IDP_AddToGroup, only you pass an item + * in the group list to be inserted after. + */ bool IDP_InsertToGroup(struct IDProperty *group, struct IDProperty *previous, struct IDProperty *pnew) ATTR_NONNULL(1 /* group */, 3 /* pnew */); +/** + * \note this does not free the property! + * + * To free the property, you have to do: + * #IDP_FreeProperty(prop); + */ void IDP_RemoveFromGroup(struct IDProperty *group, struct IDProperty *prop) ATTR_NONNULL(); +/** + * Removes the property from the group and frees it. + */ void IDP_FreeFromGroup(struct IDProperty *group, struct IDProperty *prop) ATTR_NONNULL(); struct IDProperty *IDP_GetPropertyFromGroup(const struct IDProperty *prop, const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +/** + * Same as above but ensure type match. + */ struct IDProperty *IDP_GetPropertyTypeFromGroup(const struct IDProperty *prop, const char *name, - const char type) ATTR_WARN_UNUSED_RESULT - ATTR_NONNULL(); + char type) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); /*-------- Main Functions --------*/ -struct IDProperty *IDP_GetProperties(struct ID *id, - const bool create_if_needed) ATTR_WARN_UNUSED_RESULT +/** + * Get the Group property that contains the id properties for ID id. + * + * \param create_if_needed: Set to create the group property and attach it to id if it doesn't + * exist; otherwise the function will return NULL if there's no Group property attached to the ID. + */ +struct IDProperty *IDP_GetProperties(struct ID *id, bool create_if_needed) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); struct IDProperty *IDP_CopyProperty(const struct IDProperty *prop) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); struct IDProperty *IDP_CopyProperty_ex(const struct IDProperty *prop, - const int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); + int flag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +/** + * Copy content from source #IDProperty into destination one, + * freeing destination property's content first. + */ void IDP_CopyPropertyContent(struct IDProperty *dst, struct IDProperty *src) ATTR_NONNULL(); +/** + * \param is_strict: When false treat missing items as a match. + */ bool IDP_EqualsProperties_ex(struct IDProperty *prop1, struct IDProperty *prop2, - const bool is_strict) ATTR_WARN_UNUSED_RESULT; + bool is_strict) ATTR_WARN_UNUSED_RESULT; bool IDP_EqualsProperties(struct IDProperty *prop1, struct IDProperty *prop2) ATTR_WARN_UNUSED_RESULT; -struct IDProperty *IDP_New(const char type, +/** + * Allocate a new ID. + * + * This function takes three arguments: the ID property type, a union which defines + * its initial value, and a name. + * + * The union is simple to use; see the top of BKE_idprop.h for its definition. + * An example of using this function: + * + * \code{.c} + * IDPropertyTemplate val; + * IDProperty *group, *idgroup, *color; + * group = IDP_New(IDP_GROUP, val, "group1"); // groups don't need a template. + * + * val.array.len = 4 + * val.array.type = IDP_FLOAT; + * color = IDP_New(IDP_ARRAY, val, "color1"); + * + * idgroup = IDP_GetProperties(some_id, 1); + * IDP_AddToGroup(idgroup, color); + * IDP_AddToGroup(idgroup, group); + * \endcode + * + * Note that you MUST either attach the id property to an id property group with + * IDP_AddToGroup or MEM_freeN the property, doing anything else might result in + * a memory leak. + */ +struct IDProperty *IDP_New(char type, const IDPropertyTemplate *val, const char *name) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -void IDP_FreePropertyContent_ex(struct IDProperty *prop, const bool do_id_user); +/** + * \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. + */ +void IDP_FreePropertyContent_ex(struct IDProperty *prop, bool do_id_user); void IDP_FreePropertyContent(struct IDProperty *prop); -void IDP_FreeProperty_ex(struct IDProperty *prop, const bool do_id_user); +void IDP_FreeProperty_ex(struct IDProperty *prop, bool do_id_user); void IDP_FreeProperty(struct IDProperty *prop); void IDP_ClearProperty(struct IDProperty *prop); @@ -184,18 +286,37 @@ void IDP_Reset(struct IDProperty *prop, const struct IDProperty *reference); # define IDP_Id(prop) ((ID *)(prop)->data.pointer) #endif +/** + * 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 struct IDProperty *prop); +/** + * 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 struct IDProperty *prop); +/** + * 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 struct IDProperty *prop); /** - * Call a callback for each idproperty in the hierarchy under given root one (included). - * + * Call a callback for each #IDproperty in the hierarchy under given root one (included). */ typedef void (*IDPForeachPropertyCallback)(struct IDProperty *id_property, void *user_data); +/** + * Loop through all ID properties in hierarchy of given \a id_property_root included. + * + * \note Container types (groups and arrays) are processed after applying the callback on them. + * + * \param type_filter: If not 0, only apply callback on properties of matching types, see + * IDP_TYPE_FILTER_ enum in DNA_ID.h. + */ void IDP_foreach_property(struct IDProperty *id_property_root, - const int type_filter, + int type_filter, IDPForeachPropertyCallback callback, void *user_data); @@ -230,6 +351,11 @@ typedef enum 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); +/** + * 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(struct IDPropertyUIData *ui_data, eIDPropertyUIDataType type, const struct IDPropertyUIData *other); diff --git a/source/blender/blenkernel/BKE_idprop.hh b/source/blender/blenkernel/BKE_idprop.hh new file mode 100644 index 00000000000..782fa9c7404 --- /dev/null +++ b/source/blender/blenkernel/BKE_idprop.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. + */ + +#pragma once + +#include "BKE_idprop.h" + +#include "BLI_serialize.hh" +#include "BLI_span.hh" + +namespace blender::bke::idprop { + +/** + * \brief Convert the given `properties` to `Value` objects for serialization. + * + * `IDP_ID` and `IDP_IDPARRAY` are not supported and will be ignored. + * + * UI data such as max/min will not be serialized. + */ +std::unique_ptr<io::serialize::ArrayValue> convert_to_serialize_values( + const IDProperty *properties); + +/** + * \brief Convert the given `value` to an `IDProperty`. + */ +IDProperty *convert_from_serialize_value(const blender::io::serialize::Value &value); + +class IDPropertyDeleter { + public: + void operator()(IDProperty *id_prop) + { + IDP_FreeProperty(id_prop); + } +}; + +/** \brief Allocate a new IDProperty of type IDP_INT, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, int32_t value); + +/** \brief Allocate a new IDProperty of type IDP_FLOAT, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, float value); + +/** \brief Allocate a new IDProperty of type IDP_DOUBLE, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, double value); + +/** \brief Allocate a new IDProperty of type IDP_STRING, set its name and value. */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + const StringRefNull value); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_INT. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + Span<int32_t> values); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_FLOAT. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, Span<float> values); + +/** + * \brief Allocate a new IDProperty of type IDP_ARRAY and subtype IDP_DOUBLE. + * + * \param values: The values will be copied into the IDProperty. + */ +std::unique_ptr<IDProperty, IDPropertyDeleter> create(StringRefNull prop_name, + Span<double> values); + +/** + * \brief Allocate a new IDProperty of type IDP_GROUP. + * + * \param prop_name: The name of the newly created property. + */ + +std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(StringRefNull prop_name); + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index cd656d94fce..df50f773a46 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -31,6 +31,7 @@ extern "C" { #endif +struct BPathForeachPathData; struct BlendDataReader; struct BlendExpander; struct BlendLibReader; @@ -77,12 +78,12 @@ typedef void (*IDTypeInitDataFunction)(struct ID *id); typedef void (*IDTypeCopyDataFunction)(struct Main *bmain, struct ID *id_dst, const struct ID *id_src, - const int flag); + int flag); typedef void (*IDTypeFreeDataFunction)(struct ID *id); -/** \param flag: See BKE_lib_id.h's LIB_ID_MAKELOCAL_... flags. */ -typedef void (*IDTypeMakeLocalFunction)(struct Main *bmain, struct ID *id, const int flags); +/** \param flags: See BKE_lib_id.h's LIB_ID_MAKELOCAL_... flags. */ +typedef void (*IDTypeMakeLocalFunction)(struct Main *bmain, struct ID *id, int flags); typedef void (*IDTypeForeachIDFunction)(struct ID *id, struct LibraryForeachIDData *data); @@ -100,6 +101,8 @@ typedef void (*IDTypeForeachCacheFunction)(struct ID *id, IDTypeForeachCacheFunctionCallback function_callback, void *user_data); +typedef void (*IDTypeForeachPathFunction)(struct ID *id, struct BPathForeachPathData *bpath_data); + typedef struct ID *(*IDTypeEmbeddedOwnerGetFunction)(struct Main *bmain, struct ID *id); typedef void (*IDTypeBlendWriteFunction)(struct BlendWriter *writer, @@ -149,11 +152,12 @@ typedef struct IDTypeInfo { /** Generic info flags about that data-block type. */ uint32_t flags; - /* ********** ID management callbacks ********** */ + /** + * Information and callbacks for assets, based on the type of asset. + */ + struct AssetTypeInfo *asset_type_info; - /* TODO: Note about callbacks: Ideally we could also handle here `BKE_lib_query`'s behavior, as - * well as read/write of files. However, this is a bit more involved than basic ID management - * callbacks, so we'll check on this later. */ + /* ********** ID management callbacks ********** */ /** * Initialize a new, empty calloc'ed data-block. May be NULL if there is nothing to do. @@ -189,6 +193,11 @@ typedef struct IDTypeInfo { IDTypeForeachCacheFunction foreach_cache; /** + * Iterator over all file paths of given ID. + */ + IDTypeForeachPathFunction foreach_path; + + /** * For embedded IDs, return their owner ID. */ IDTypeEmbeddedOwnerGetFunction owner_get; @@ -274,6 +283,7 @@ extern IDTypeInfo IDType_ID_PT; extern IDTypeInfo IDType_ID_VO; extern IDTypeInfo IDType_ID_SIM; +/** Empty shell mostly, but needed for read code. */ extern IDTypeInfo IDType_ID_LINK_PLACEHOLDER; /* ********** Helpers/Utils API. ********** */ @@ -282,35 +292,104 @@ extern IDTypeInfo IDType_ID_LINK_PLACEHOLDER; void BKE_idtype_init(void); /* General helpers. */ -const struct IDTypeInfo *BKE_idtype_get_info_from_idcode(const short id_code); +const struct IDTypeInfo *BKE_idtype_get_info_from_idcode(short id_code); 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); +/** + * Convert an \a idcode into a name. + * + * \param idcode: The code to convert. + * \return A static string representing the name of the code. + */ +const char *BKE_idtype_idcode_to_name(short idcode); +/** + * Convert an \a idcode into a name (plural). + * + * \param idcode: The code to convert. + * \return A static string representing the name of the code. + */ +const char *BKE_idtype_idcode_to_name_plural(short idcode); +/** + * Convert an \a idcode into its translations' context. + * + * \param idcode: The code to convert. + * \return A static string representing the i18n context of the code. + */ +const char *BKE_idtype_idcode_to_translation_context(short idcode); -bool BKE_idtype_idcode_is_valid(const short idcode); +/** + * Return if the ID code is a valid ID code. + * + * \param idcode: The code to check. + * \return Boolean, 0 when invalid. + */ +bool BKE_idtype_idcode_is_valid(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); +/** + * Check if an ID type is linkable. + * + * \param idcode: The IDType code to check. + * \return Boolean, false when non linkable, true otherwise. + */ +bool BKE_idtype_idcode_is_linkable(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(short idcode); +/** + * 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(short idcode); /* Macro currently, since any linkable IDtype should be localizable. */ #define BKE_idtype_idcode_is_localizable BKE_idtype_idcode_is_linkable +/** + * Convert an ID-type name into an \a idcode (ie. #ID_SCE) + * + * \param idtype_name: The ID-type's "user visible name" to convert. + * \return The \a idcode for the name, or 0 if invalid. + */ short BKE_idtype_idcode_from_name(const char *idtype_name); -uint64_t BKE_idtype_idcode_to_idfilter(const short idcode); -short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter); +/** + * Convert an \a idcode into an \a idfilter (e.g. #ID_OB -> #FILTER_ID_OB). + */ +uint64_t BKE_idtype_idcode_to_idfilter(short idcode); +/** + * Convert an \a idfilter into an \a idcode (e.g. #FILTER_ID_OB -> #ID_OB). + */ +short BKE_idtype_idcode_from_idfilter(uint64_t idfilter); -int BKE_idtype_idcode_to_index(const short idcode); -short BKE_idtype_idcode_from_index(const int index); +/** + * Convert an \a idcode into an index (e.g. #ID_OB -> #INDEX_ID_OB). + */ +int BKE_idtype_idcode_to_index(short idcode); +/** + * Get an \a idcode from an index (e.g. #INDEX_ID_OB -> #ID_OB). + */ +short BKE_idtype_idcode_from_index(int index); +/** + * Return an ID code and steps the index forward 1. + * + * \param index: start as 0. + * \return the code, 0 when all codes have been returned. + */ short BKE_idtype_idcode_iter_step(int *index); /* Some helpers/wrappers around callbacks defined in #IDTypeInfo, dealing e.g. with embedded IDs. * XXX Ideally those would rather belong to #BKE_lib_id, but using callback function pointers makes * this hard to do properly if we want to avoid headers includes in headers. */ +/** + * Wrapper around #IDTypeInfo foreach_cache that also handles embedded IDs. + */ void BKE_idtype_id_foreach_cache(struct ID *id, IDTypeForeachCacheFunctionCallback function_callback, void *user_data); diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h index b62ad3ad24a..80c6b155be0 100644 --- a/source/blender/blenkernel/BKE_image.h +++ b/source/blender/blenkernel/BKE_image.h @@ -36,6 +36,7 @@ struct ImageFormatData; struct ImagePool; struct ImageTile; struct ImbFormatOptions; +struct ListBase; struct Main; struct Object; struct RenderResult; @@ -47,15 +48,19 @@ struct anim; #define IMA_MAX_SPACE 64 #define IMA_UDIM_MAX 2000 -void BKE_images_init(void); -void BKE_images_exit(void); - void BKE_image_free_packedfiles(struct Image *image); void BKE_image_free_views(struct Image *image); void BKE_image_free_buffers(struct Image *image); +/** + * Simply free the image data from memory, + * on display the image can load again (except for render buffers). + */ void BKE_image_free_buffers_ex(struct Image *image, bool do_lock); void BKE_image_free_gputextures(struct Image *ima); -/* call from library */ +/** + * Free (or release) any data used by this image (does not free the image itself). + * \note Call from library. + */ void BKE_image_free_data(struct Image *image); typedef void(StampCallback)(void *data, const char *propname, char *propvalue, int len); @@ -69,6 +74,9 @@ void BKE_render_result_stamp_info(struct Scene *scene, * The caller is responsible for freeing the allocated memory. */ struct StampData *BKE_stamp_info_from_scene_static(const struct Scene *scene); +/** + * Check whether the given metadata field name translates to a known field of a stamp. + */ bool BKE_stamp_is_known_field(const char *field_name); void BKE_imbuf_stamp_info(struct RenderResult *rr, struct ImBuf *ibuf); void BKE_stamp_info_from_imbuf(struct RenderResult *rr, struct ImBuf *ibuf); @@ -93,46 +101,60 @@ int BKE_imbuf_write_stamp(struct Scene *scene, struct ImBuf *ibuf, const char *name, const struct ImageFormatData *imf); +/** + * \note imf->planes is ignored here, its assumed the image channels are already set. + */ void BKE_imbuf_write_prepare(struct ImBuf *ibuf, const struct ImageFormatData *imf); int BKE_imbuf_write(struct ImBuf *ibuf, const char *name, const struct ImageFormatData *imf); +/** + * Same as #BKE_imbuf_write() but crappy workaround not to permanently modify _some_, + * values in the imbuf. + */ int BKE_imbuf_write_as(struct ImBuf *ibuf, const char *name, struct ImageFormatData *imf, - const bool save_copy); + bool save_copy); void BKE_image_path_from_imformat(char *string, const char *base, const char *relbase, int frame, const struct ImageFormatData *im_format, - const bool use_ext, - const bool use_frames, + bool use_ext, + bool use_frames, const char *suffix); void BKE_image_path_from_imtype(char *string, const char *base, const char *relbase, int frame, - const char imtype, - const bool use_ext, - const bool use_frames, + char imtype, + bool use_ext, + bool use_frames, const char *suffix); int BKE_image_path_ensure_ext_from_imformat(char *string, const struct ImageFormatData *im_format); -int BKE_image_path_ensure_ext_from_imtype(char *string, const char imtype); -char BKE_image_ftype_to_imtype(const int ftype, const struct ImbFormatOptions *options); -int BKE_image_imtype_to_ftype(const char imtype, struct ImbFormatOptions *r_options); - -bool BKE_imtype_is_movie(const char imtype); -bool BKE_imtype_supports_zbuf(const char imtype); -bool BKE_imtype_supports_compress(const char imtype); -bool BKE_imtype_supports_quality(const char imtype); -bool BKE_imtype_requires_linear_float(const char imtype); -char BKE_imtype_valid_channels(const char imtype, bool write_file); -char BKE_imtype_valid_depths(const char imtype); +int BKE_image_path_ensure_ext_from_imtype(char *string, char imtype); +char BKE_image_ftype_to_imtype(int ftype, const struct ImbFormatOptions *options); +int BKE_image_imtype_to_ftype(char imtype, struct ImbFormatOptions *r_options); + +bool BKE_imtype_is_movie(char imtype); +bool BKE_imtype_supports_zbuf(char imtype); +bool BKE_imtype_supports_compress(char imtype); +bool BKE_imtype_supports_quality(char imtype); +bool BKE_imtype_requires_linear_float(char imtype); +char BKE_imtype_valid_channels(char imtype, bool write_file); +char BKE_imtype_valid_depths(char imtype); +/** + * String is from command line `--render-format` argument, + * keep in sync with `creator_args.c` help info. + */ char BKE_imtype_from_arg(const char *arg); void BKE_imformat_defaults(struct ImageFormatData *im_format); void BKE_imbuf_to_image_format(struct ImageFormatData *im_format, const struct ImBuf *imbuf); +/** + * Used by sequencer too. + */ struct anim *openanim(const char *name, int flags, int streamindex, @@ -153,10 +175,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 @@ -171,11 +189,18 @@ struct RenderResult; #define IMA_CHAN_FLAG_RGB 2 #define IMA_CHAN_FLAG_ALPHA 4 -/* checks whether there's an image buffer for given image and user */ +/** + * Checks whether there's an image buffer for given image and user. + */ bool BKE_image_has_ibuf(struct Image *ima, struct ImageUser *iuser); -/* same as above, but can be used to retrieve images being rendered in - * a thread safe way, always call both acquire and release */ +/** + * Return image buffer for given image and user: + * - will lock render result if image type is render result and lock is not NULL + * - will return NULL if image is NULL or image type is render or composite result and lock is NULL + * + * References the result, #BKE_image_release_ibuf should be used to de-reference. + */ struct ImBuf *BKE_image_acquire_ibuf(struct Image *ima, struct ImageUser *iuser, void **r_lock); void BKE_image_release_ibuf(struct Image *ima, struct ImBuf *ibuf, void *lock); @@ -186,17 +211,28 @@ struct ImBuf *BKE_image_pool_acquire_ibuf(struct Image *ima, struct ImagePool *pool); void BKE_image_pool_release_ibuf(struct Image *ima, struct ImBuf *ibuf, struct ImagePool *pool); -/* set an alpha mode based on file extension */ +/** + * Set an alpha mode based on file extension. + */ char BKE_image_alpha_mode_from_extension_ex(const char *filepath); void BKE_image_alpha_mode_from_extension(struct Image *image); -/* returns a new image or NULL if it can't load */ +/** + * Returns a new image or NULL if it can't load. + */ struct Image *BKE_image_load(struct Main *bmain, const char *filepath); -/* returns existing Image when filename/type is same (frame optional) */ +/** + * Returns existing Image when filename/type is same. + * + * Checks if image was already loaded, then returns same image otherwise creates new + * (does not load ibuf itself). + */ struct Image *BKE_image_load_exists_ex(struct Main *bmain, const char *filepath, bool *r_exists); struct Image *BKE_image_load_exists(struct Main *bmain, const char *filepath); -/* adds image, adds ibuf, generates color or pattern */ +/** + * Adds new image block, creates ImBuf and initializes color. + */ struct Image *BKE_image_add_generated(struct Main *bmain, unsigned int width, unsigned int height, @@ -205,13 +241,18 @@ struct Image *BKE_image_add_generated(struct Main *bmain, int floatbuf, short gen_type, const float color[4], - const bool stereo3d, - const bool is_data, - const bool tiled); -/* adds image from imbuf, owns imbuf */ + bool stereo3d, + bool is_data, + bool tiled); +/** + * Create an image from ibuf. The reference-count of ibuf is increased, + * caller should take care to drop its reference by calling #IMB_freeImBuf if needed. + */ struct Image *BKE_image_add_from_imbuf(struct Main *bmain, struct ImBuf *ibuf, const char *name); -/* for reload, refresh, pack */ +/** + * For reload, refresh, pack. + */ void BKE_imageuser_default(struct ImageUser *iuser); void BKE_image_init_imageuser(struct Image *ima, struct ImageUser *iuser); void BKE_image_signal(struct Main *bmain, struct Image *ima, struct ImageUser *iuser, int signal); @@ -223,89 +264,156 @@ void BKE_image_walk_all_users(const struct Main *mainp, struct ImageUser *iuser, void *customdata)); -/* ensures an Image exists for viewing nodes or render */ +/** + * Ensures an Image exists for viewing nodes or render + * forces existence of 1 Image for render-output or nodes, returns Image. + * + * \param name: Only for default, when making new one. + */ struct Image *BKE_image_ensure_viewer(struct Main *bmain, int type, const char *name); -/* ensures the view node cache is compatible with the scene views */ +/** + * Ensures the view node cache is compatible with the scene views. + * Reset the image cache and views when the Viewer Nodes views don't match the scene views. + */ void BKE_image_ensure_viewer_views(const struct RenderData *rd, struct Image *ima, struct ImageUser *iuser); -/* called on frame change or before render */ +/** + * Called on frame change or before render. + */ void BKE_image_user_frame_calc(struct Image *ima, struct ImageUser *iuser, int cfra); int BKE_image_user_frame_get(const struct ImageUser *iuser, int cfra, bool *r_is_in_range); void BKE_image_user_file_path(struct ImageUser *iuser, struct Image *ima, char *path); +void BKE_image_user_file_path_ex(struct ImageUser *iuser, + struct Image *ima, + char *path, + bool resolve_udim); void BKE_image_editors_update_frame(const struct Main *bmain, int cfra); -/* dependency graph update for image user users */ +/** + * Dependency graph update for image user users. + */ bool BKE_image_user_id_has_animation(struct ID *id); void BKE_image_user_id_eval_animation(struct Depsgraph *depsgraph, struct ID *id); -/* sets index offset for multilayer files */ +/** + * Sets index offset for multi-layer files and because rendered results use fake layer/passes, + * don't correct for wrong indices here. + */ struct RenderPass *BKE_image_multilayer_index(struct RenderResult *rr, struct ImageUser *iuser); -/* sets index offset for multiview files */ +/** + * Sets index offset for multi-view files. + */ void BKE_image_multiview_index(struct Image *ima, struct ImageUser *iuser); -/* for multilayer images as well as for render-viewer */ +/** + * For multi-layer images as well as for render-viewer + * and because rendered results use fake layer/passes, don't correct for wrong indices here. + */ bool BKE_image_is_multilayer(struct Image *ima); bool BKE_image_is_multiview(struct Image *ima); 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, - const int flags); + int flags); -/* goes over all textures that use images */ +/** + * Goes over all textures that use images. + */ void BKE_image_free_all_textures(struct Main *bmain); -/* does one image! */ +/** + * Operates on one image only! + * \param except_frame: This is weak, only works for sequences without offset. + */ void BKE_image_free_anim_ibufs(struct Image *ima, int except_frame); -/* does all images with type MOVIE or SEQUENCE */ +/** + * Does all images with type MOVIE or SEQUENCE. + */ void BKE_image_all_free_anim_ibufs(struct Main *bmain, int cfra); void BKE_image_free_all_gputextures(struct Main *bmain); +/** + * Same as above but only free animated images. + */ void BKE_image_free_anim_gputextures(struct Main *bmain); void BKE_image_free_old_gputextures(struct Main *bmain); +/** + * Pack image to memory. + */ bool BKE_image_memorypack(struct Image *ima); void BKE_image_packfiles(struct ReportList *reports, struct Image *ima, const char *basepath); void BKE_image_packfiles_from_mem(struct ReportList *reports, struct Image *ima, char *data, - const size_t data_len); + 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, int len_label); +/** + * Checks whether the given filepath refers to a UDIM tiled texture. + * If yes, the range from the lowest to the highest tile is returned. + * + * `filepath` may be modified to ensure a UDIM token is present. + * `tiles` may be filled even if the result ultimately is false! + */ +bool BKE_image_get_tile_info(char *filepath, + struct ListBase *tiles, + int *tile_start, + int *tile_range); + 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); @@ -320,6 +428,41 @@ bool BKE_image_fill_tile(struct Image *ima, int planes, bool is_float); +typedef enum { + UDIM_TILE_FORMAT_NONE = 0, + UDIM_TILE_FORMAT_UDIM = 1, + UDIM_TILE_FORMAT_UVTILE = 2 +} eUDIM_TILE_FORMAT; + +/** + * Ensures that `filename` contains a UDIM token if we find a supported format pattern. + * \note This must only be the name component (without slashes). + */ +void BKE_image_ensure_tile_token(char *filename); + +/** + * When provided with an absolute virtual filepath, check to see if at least + * one concrete file exists. + * Note: This function requires directory traversal and may be inefficient in time-critical, + * or iterative, code paths. + */ +bool BKE_image_tile_filepath_exists(const char *filepath); + +/** + * Retrieves the UDIM token format and returns the pattern from the provided `filepath`. + * The returned pattern is typically passed to either `BKE_image_get_tile_number_from_filepath` or + * `BKE_image_set_filepath_from_tile_number`. + */ +char *BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format); +bool BKE_image_get_tile_number_from_filepath(const char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int *r_tile_number); +void BKE_image_set_filepath_from_tile_number(char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int tile_number); + struct ImageTile *BKE_image_get_tile(struct Image *ima, int tile_number); struct ImageTile *BKE_image_get_tile_from_iuser(struct Image *ima, const struct ImageUser *iuser); @@ -327,6 +470,9 @@ int BKE_image_get_tile_from_pos(struct Image *ima, const float uv[2], float r_uv[2], float r_ofs[2]); +/** + * Return the tile_number for the closest UDIM tile. + */ int BKE_image_find_nearest_tile(const struct Image *image, const float co[2]); void BKE_image_get_size(struct Image *image, struct ImageUser *iuser, int *r_width, int *r_height); @@ -334,6 +480,7 @@ void BKE_image_get_size_fl(struct Image *image, struct ImageUser *iuser, float r void BKE_image_get_aspect(struct Image *image, float *r_aspx, float *r_aspy); /* image_gen.c */ + void BKE_image_buf_fill_color( unsigned char *rect, float *rect_float, int width, int height, const float color[4]); void BKE_image_buf_fill_checker(unsigned char *rect, float *rect_float, int width, int height); @@ -343,36 +490,64 @@ void BKE_image_buf_fill_checker_color(unsigned char *rect, int height); /* Cycles hookup */ + unsigned char *BKE_image_get_pixels_for_frame(struct Image *image, int frame, int tile); float *BKE_image_get_float_pixels_for_frame(struct Image *image, int frame, int tile); /* Image modifications */ + bool BKE_image_is_dirty(struct Image *image); void BKE_image_mark_dirty(struct Image *image, struct ImBuf *ibuf); bool BKE_image_buffer_format_writable(struct ImBuf *ibuf); + bool BKE_image_is_dirty_writable(struct Image *image, bool *is_format_writable); -/* Guess offset for the first frame in the sequence */ +/** + * 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); +/** + * Checks the image buffer changes with time (not keyframed values). + */ bool BKE_image_is_animated(struct Image *image); +/** + * Checks whether the image consists of multiple buffers. + */ bool BKE_image_has_multiple_ibufs(struct Image *image); void BKE_image_file_format_set(struct Image *image, int ftype, const struct ImbFormatOptions *options); bool BKE_image_has_loaded_ibuf(struct Image *image); +/** + * References the result, #BKE_image_release_ibuf is to be called to de-reference. + * Use lock=NULL when calling #BKE_image_release_ibuf(). + */ struct ImBuf *BKE_image_get_ibuf_with_name(struct Image *image, const char *name); +/** + * References the result, #BKE_image_release_ibuf is to be called to de-reference. + * Use lock=NULL when calling #BKE_image_release_ibuf(). + * + * TODO(sergey): This is actually "get first item from the cache", which is + * not so much predictable. But using first loaded image buffer + * was also malicious logic and all the areas which uses this + * function are to be re-considered. + */ struct ImBuf *BKE_image_get_first_ibuf(struct Image *image); -/* Not to be use directly. */ +/** + * 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); @@ -382,14 +557,33 @@ struct GPUTexture *BKE_image_get_gpu_tiles(struct Image *image, struct GPUTexture *BKE_image_get_gpu_tilemap(struct Image *image, struct ImageUser *iuser, struct ImBuf *ibuf); +/** + * Is the alpha of the `GPUTexture` for a given image/ibuf premultiplied. + */ bool BKE_image_has_gpu_texture_premultiplied_alpha(struct Image *image, struct ImBuf *ibuf); +/** + * Partial update of texture for texture painting. + * This is often much quicker than fully updating the texture for high resolution images. + */ void BKE_image_update_gputexture( struct Image *ima, struct ImageUser *iuser, int x, int y, int w, int h); +/** + * Mark areas on the #GPUTexture that needs to be updated. The areas are marked in chunks. + * The next time the #GPUTexture is used these tiles will be refreshes. This saves time + * when writing to the same place multiple times This happens for during foreground rendering. + */ void BKE_image_update_gputexture_delayed( struct Image *ima, struct ImBuf *ibuf, int x, int y, int w, int h); +/** + * Called on entering and exiting texture paint mode, + * temporary disabling/enabling mipmapping on all images for quick texture + * updates with glTexSubImage2D. images that didn't change don't have to be re-uploaded to OpenGL. + */ void BKE_image_paint_set_mipmap(struct Main *bmain, bool mipmap); -/* Delayed free of OpenGL buffers by main thread */ +/** + * Delayed free of OpenGL buffers by main thread. + */ void BKE_image_free_unused_gpu_textures(void); struct RenderSlot *BKE_image_add_renderslot(struct Image *ima, const char *name); diff --git a/source/blender/blenkernel/BKE_ipo.h b/source/blender/blenkernel/BKE_ipo.h index f4871c83caf..5899db6c6ce 100644 --- a/source/blender/blenkernel/BKE_ipo.h +++ b/source/blender/blenkernel/BKE_ipo.h @@ -28,6 +28,19 @@ extern "C" { struct Main; +/** + * Called from #do_versions() in `readfile.c` to convert the old 'IPO/adrcode' system + * to the new 'Animato/RNA' system. + * + * The basic method used here, is to loop over data-blocks which have IPO-data, + * and add those IPO's to new AnimData blocks as Actions. + * Action/NLA data only works well for Objects, so these only need to be checked for there. + * + * Data that has been converted should be freed immediately, which means that it is immediately + * clear which data-blocks have yet to be converted, and also prevent freeing errors when we exit. + * + * \note Currently done after all file reading. + */ void do_versions_ipos_to_animato(struct Main *main); /* --------------------- xxx stuff ------------------------ */ diff --git a/source/blender/blenkernel/BKE_key.h b/source/blender/blenkernel/BKE_key.h index cb4fc607703..07e816558df 100644 --- a/source/blender/blenkernel/BKE_key.h +++ b/source/blender/blenkernel/BKE_key.h @@ -36,42 +36,90 @@ struct Object; extern "C" { #endif +/** + * Free (or release) any data used by this shapekey (does not free the key itself). + */ 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); +/** + * 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(struct Key *key); void key_curve_position_weights(float t, float data[4], int type); +/** + * First derivative. + */ void key_curve_tangent_weights(float t, float data[4], int type); +/** + * Second derivative. + */ void key_curve_normal_weights(float t, float data[4], int type); +/** + * Returns key coordinates (+ tilt) when key applied, NULL otherwise. + */ float *BKE_key_evaluate_object_ex(struct Object *ob, int *r_totelem, float *arr, size_t arr_size); float *BKE_key_evaluate_object(struct Object *ob, int *r_totelem); -int BKE_keyblock_element_count_from_shape(const struct Key *key, const int shape_index); +/** + * \param shape_index: The index to use or all (when -1). + */ +int BKE_keyblock_element_count_from_shape(const struct Key *key, int shape_index); int BKE_keyblock_element_count(const struct Key *key); -size_t BKE_keyblock_element_calc_size_from_shape(const struct Key *key, const int shape_index); +/** + * \param shape_index: The index to use or all (when -1). + */ +size_t BKE_keyblock_element_calc_size_from_shape(const struct Key *key, int shape_index); size_t BKE_keyblock_element_calc_size(const struct Key *key); -bool BKE_key_idtype_support(const short id_type); +bool BKE_key_idtype_support(short id_type); struct Key **BKE_key_from_id_p(struct ID *id); struct Key *BKE_key_from_id(struct ID *id); struct Key **BKE_key_from_object_p(const struct Object *ob); struct Key *BKE_key_from_object(const struct Object *ob); +/** + * Only the active key-block. + */ struct KeyBlock *BKE_keyblock_from_object(struct Object *ob); struct KeyBlock *BKE_keyblock_from_object_reference(struct Object *ob); struct KeyBlock *BKE_keyblock_add(struct Key *key, const char *name); -struct KeyBlock *BKE_keyblock_add_ctime(struct Key *key, const char *name, const bool do_force); +/** + * \note sorting is a problematic side effect in some cases, + * better only do this explicitly by having its own function, + * + * \param key: The key datablock to add to. + * \param name: Optional name for the new keyblock. + * \param do_force: always use ctime even for relative keys. + */ +struct KeyBlock *BKE_keyblock_add_ctime(struct Key *key, const char *name, bool do_force); +/** + * Get the appropriate #KeyBlock given an index. + */ struct KeyBlock *BKE_keyblock_from_key(struct Key *key, int index); +/** + * Get the appropriate #KeyBlock given a name to search for. + */ struct KeyBlock *BKE_keyblock_find_name(struct Key *key, const char name[]); +/** + * \brief copy shape-key attributes, but not key data or name/UID. + */ void BKE_keyblock_copy_settings(struct KeyBlock *kb_dst, const struct KeyBlock *kb_src); +/** + * Get RNA-Path for 'value' setting of the given shape-key. + * \note the user needs to free the returned string once they're finished with it. + */ 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. */ + 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); @@ -88,6 +136,15 @@ void BKE_keyblock_convert_to_curve(struct KeyBlock *kb, struct Curve *cu, struct void BKE_keyblock_update_from_mesh(struct Mesh *me, struct KeyBlock *kb); void BKE_keyblock_convert_from_mesh(struct Mesh *me, struct Key *key, struct KeyBlock *kb); void BKE_keyblock_convert_to_mesh(struct KeyBlock *kb, struct 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 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. + */ void BKE_keyblock_mesh_calc_normals(struct KeyBlock *kb, struct Mesh *mesh, float (*r_vertnors)[3], @@ -107,29 +164,53 @@ void BKE_keyblock_update_from_offset(struct Object *ob, const float (*ofs)[3]); /* other management */ + +/** + * Move shape key from org_index to new_index. Safe, clamps index to valid range, + * updates reference keys, the object's active shape index, + * the 'frame' value in case of absolute keys, etc. + * Note indices are expected in real values (not 'fake' shapenr +1 ones). + * + * \param org_index: if < 0, current object's active shape will be used as skey to move. + * \return true if something was done, else false. + */ bool BKE_keyblock_move(struct Object *ob, int org_index, int new_index); -bool BKE_keyblock_is_basis(struct Key *key, const int index); +/** + * Check if given key-block (as index) is used as basis by others in given key. + */ +bool BKE_keyblock_is_basis(struct Key *key, int index); /* -------------------------------------------------------------------- */ /** \name Key-Block Data Access * \{ */ -void BKE_keyblock_data_get_from_shape(const struct Key *key, - float (*arr)[3], - const int shape_index); +/** + * \param shape_index: The index to use or all (when -1). + */ +void BKE_keyblock_data_get_from_shape(const struct Key *key, float (*arr)[3], int shape_index); void BKE_keyblock_data_get(const struct Key *key, float (*arr)[3]); +/** + * Set the data to all key-blocks (or shape_index if != -1). + */ void BKE_keyblock_data_set_with_mat4(struct Key *key, - const int shape_index, + int shape_index, const float (*coords)[3], const float mat[4][4]); +/** + * Set the data for all key-blocks (or shape_index if != -1), + * transforming by \a mat. + */ void BKE_keyblock_curve_data_set_with_mat4(struct Key *key, const struct ListBase *nurb, - const int shape_index, + int shape_index, const void *data, const float mat[4][4]); -void BKE_keyblock_data_set(struct Key *key, const int shape_index, const void *data); +/** + * Set the data for all key-blocks (or shape_index if != -1). + */ +void BKE_keyblock_data_set(struct Key *key, int shape_index, const void *data); /** \} */ diff --git a/source/blender/blenkernel/BKE_keyconfig.h b/source/blender/blenkernel/BKE_keyconfig.h index 1cacbf61976..132994ede3a 100644 --- a/source/blender/blenkernel/BKE_keyconfig.h +++ b/source/blender/blenkernel/BKE_keyconfig.h @@ -43,10 +43,12 @@ typedef struct wmKeyConfigPrefType_Runtime { typedef struct wmKeyConfigPrefType_Runtime wmKeyConfigPrefType_Runtime; #endif -/* KeyConfig preferences (UserDef). */ +/* KeyConfig preferences (#UserDef). */ + struct wmKeyConfigPref *BKE_keyconfig_pref_ensure(struct UserDef *userdef, const char *kc_idname); /* KeyConfig preferences (RNA). */ + struct wmKeyConfigPrefType_Runtime *BKE_keyconfig_pref_type_find(const char *idname, bool quiet); void BKE_keyconfig_pref_type_add(struct wmKeyConfigPrefType_Runtime *kpt_rt); void BKE_keyconfig_pref_type_remove(const struct wmKeyConfigPrefType_Runtime *kpt_rt); @@ -55,6 +57,10 @@ void BKE_keyconfig_pref_type_init(void); void BKE_keyconfig_pref_type_free(void); /* Versioning. */ + +/** + * Set select mouse, for versioning code. + */ void BKE_keyconfig_pref_set_select_mouse(struct UserDef *userdef, int value, bool override); struct wmKeyConfigFilterItemParams { @@ -67,6 +73,10 @@ void BKE_keyconfig_keymap_filter_item(struct wmKeyMap *keymap, const struct wmKeyConfigFilterItemParams *params, bool (*filter_fn)(struct wmKeyMapItem *kmi, void *user_data), void *user_data); +/** + * Filter & optionally remove key-map items, + * intended for versioning, but may be used in other situations too. + */ void BKE_keyconfig_pref_filter_items(struct UserDef *userdef, const struct wmKeyConfigFilterItemParams *params, bool (*filter_fn)(struct wmKeyMapItem *kmi, void *user_data), diff --git a/source/blender/blenkernel/BKE_lattice.h b/source/blender/blenkernel/BKE_lattice.h index 02fa8b306d3..35260aa3852 100644 --- a/source/blender/blenkernel/BKE_lattice.h +++ b/source/blender/blenkernel/BKE_lattice.h @@ -69,15 +69,11 @@ void BKE_lattice_transform(struct Lattice *lt, const float mat[4][4], bool do_ke bool BKE_lattice_is_any_selected(const struct Lattice *lt); -int BKE_lattice_index_from_uvw(struct Lattice *lt, const int u, const int v, const int w); -void BKE_lattice_index_to_uvw(struct Lattice *lt, const int index, int *r_u, int *r_v, int *r_w); -int BKE_lattice_index_flip( - struct Lattice *lt, const int index, const bool flip_u, const bool flip_v, const bool flip_w); -void BKE_lattice_bitmap_from_flag(struct Lattice *lt, - unsigned int *bitmap, - const uint8_t flag, - const bool clear, - const bool respecthide); +int BKE_lattice_index_from_uvw(struct Lattice *lt, int u, int v, int w); +void BKE_lattice_index_to_uvw(struct Lattice *lt, int index, int *r_u, int *r_v, int *r_w); +int BKE_lattice_index_flip(struct Lattice *lt, int index, bool flip_u, bool flip_v, bool flip_w); +void BKE_lattice_bitmap_from_flag( + struct Lattice *lt, unsigned int *bitmap, uint8_t flag, bool clear, bool respecthide); /* **** Depsgraph evaluation **** */ @@ -110,28 +106,29 @@ void BKE_lattice_deform_data_destroy(struct LatticeDeformData *lattice_deform_da void BKE_lattice_deform_coords(const struct Object *ob_lattice, const struct Object *ob_target, float (*vert_coords)[3], - const int vert_coords_len, - const short flag, + int vert_coords_len, + short flag, const char *defgrp_name, float fac); void BKE_lattice_deform_coords_with_mesh(const struct Object *ob_lattice, const struct Object *ob_target, float (*vert_coords)[3], - const int vert_coords_len, - const short flag, + int vert_coords_len, + short flag, const char *defgrp_name, - const float fac, + float fac, const struct Mesh *me_target); void BKE_lattice_deform_coords_with_editmesh(const struct Object *ob_lattice, const struct Object *ob_target, float (*vert_coords)[3], - const int vert_coords_len, - const short flag, + int vert_coords_len, + short flag, const char *defgrp_name, - const float fac, + float fac, struct BMEditMesh *em_target); + /** \} */ #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_layer.h b/source/blender/blenkernel/BKE_layer.h index c8af1a91725..accdfe1ca25 100644 --- a/source/blender/blenkernel/BKE_layer.h +++ b/source/blender/blenkernel/BKE_layer.h @@ -52,23 +52,57 @@ typedef enum eViewLayerCopyMethod { VIEWLAYER_ADD_COPY = 2, } eViewLayerCopyMethod; +/** + * Returns the default view layer to view in work-spaces if there is + * none linked to the workspace yet. + */ struct ViewLayer *BKE_view_layer_default_view(const struct Scene *scene); +/** + * Returns the default view layer to render if we need to render just one. + */ struct ViewLayer *BKE_view_layer_default_render(const struct Scene *scene); +/** + * Returns view layer with matching name, or NULL if not found. + */ struct ViewLayer *BKE_view_layer_find(const struct Scene *scene, const char *layer_name); +/** + * Add a new view layer by default, a view layer has the master collection. + */ struct ViewLayer *BKE_view_layer_add(struct Scene *scene, const char *name, struct ViewLayer *view_layer_source, - const int type); + int type); /* DEPRECATED */ +/** + * This is a placeholder to know which areas of the code need to be addressed + * for the Workspace changes. Never use this, you should typically get the + * active layer from the context or window. + */ struct ViewLayer *BKE_view_layer_context_active_PLACEHOLDER(const struct Scene *scene); void BKE_view_layer_free(struct ViewLayer *view_layer); -void BKE_view_layer_free_ex(struct ViewLayer *view_layer, const bool do_id_user); +/** + * Free (or release) any data used by this #ViewLayer. + */ +void BKE_view_layer_free_ex(struct ViewLayer *view_layer, bool do_id_user); -void BKE_view_layer_selected_objects_tag(struct ViewLayer *view_layer, const int tag); +/** + * Tag all the selected objects of a render-layer. + */ +void BKE_view_layer_selected_objects_tag(struct ViewLayer *view_layer, int tag); +/** + * Fallback for when a Scene has no camera to use. + * + * \param view_layer: in general you want to use the same #ViewLayer that is used for depsgraph. + * If rendering you pass the scene active layer, when viewing in the viewport + * you want to get #ViewLayer from context. + */ struct Object *BKE_view_layer_camera_find(struct ViewLayer *view_layer); +/** + * Find the #ViewLayer a #LayerCollection belongs to. + */ struct ViewLayer *BKE_view_layer_find_from_collection(const struct Scene *scene, struct LayerCollection *lc); struct Base *BKE_view_layer_base_find(struct ViewLayer *view_layer, struct Object *ob); @@ -76,47 +110,100 @@ void BKE_view_layer_base_deselect_all(struct ViewLayer *view_layer); void BKE_view_layer_base_select_and_set_active(struct ViewLayer *view_layer, struct Base *selbase); +/** + * Only copy internal data of #ViewLayer from source to already allocated/initialized destination. + * + * \param flag: Copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more). + */ void BKE_view_layer_copy_data(struct Scene *scene_dst, const struct Scene *scene_src, struct ViewLayer *view_layer_dst, const struct ViewLayer *view_layer_src, - const int flag); + int flag); void BKE_view_layer_rename(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, const char *name); +/** + * Get the active collection + */ struct LayerCollection *BKE_layer_collection_get_active(struct ViewLayer *view_layer); +/** + * Activate collection + */ bool BKE_layer_collection_activate(struct ViewLayer *view_layer, struct LayerCollection *lc); +/** + * Activate first parent collection. + */ struct LayerCollection *BKE_layer_collection_activate_parent(struct ViewLayer *view_layer, struct LayerCollection *lc); +/** + * Get the total number of collections (including all the nested collections) + */ int BKE_layer_collection_count(const struct ViewLayer *view_layer); -struct LayerCollection *BKE_layer_collection_from_index(struct ViewLayer *view_layer, - const int index); +/** + * Get the collection for a given index. + */ +struct LayerCollection *BKE_layer_collection_from_index(struct ViewLayer *view_layer, int index); +/** + * \return -1 if not found. + */ 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); +/** + * Helper to fix older pre-2.80 blend-files. + * + * Ensures the given `view_layer` as a valid first-level layer collection, i.e. a single one + * matching the scene's master collection. This is a requirement for `BKE_layer_collection_sync`. + */ +void BKE_layer_collection_doversion_2_80(const struct Scene *scene, struct ViewLayer *view_layer); + void BKE_main_collection_sync(const struct Main *bmain); void BKE_scene_collection_sync(const struct Scene *scene); +/** + * Update view layer collection tree from collections used in the scene. + * This is used when collections are removed or added, both while editing + * and on file loaded in case linked data changed or went missing. + */ void BKE_layer_collection_sync(const struct Scene *scene, struct ViewLayer *view_layer); void BKE_layer_collection_local_sync(struct ViewLayer *view_layer, const struct View3D *v3d); +/** + * Sync the local collection for all the 3D Viewports. + */ void BKE_layer_collection_local_sync_all(const struct Main *bmain); void BKE_main_collection_sync_remap(const struct Main *bmain); +/** + * Return the first matching #LayerCollection in the #ViewLayer for the Collection. + */ struct LayerCollection *BKE_layer_collection_first_from_scene_collection( const struct ViewLayer *view_layer, const struct Collection *collection); +/** + * See if view layer has the scene collection linked directly, or indirectly (nested). + */ bool BKE_view_layer_has_collection(const struct ViewLayer *view_layer, const struct Collection *collection); +/** + * See if the object is in any of the scene layers of the scene. + */ bool BKE_scene_has_object(struct Scene *scene, struct Object *ob); -/* selection and hiding */ +/* Selection and hiding. */ +/** + * Select all the objects of this layer collection + * + * It also select the objects that are in nested collections. + * \note Recursive. + */ bool BKE_layer_collection_objects_select(struct ViewLayer *view_layer, struct LayerCollection *lc, bool deselect); @@ -125,28 +212,54 @@ bool BKE_layer_collection_has_selected_objects(struct ViewLayer *view_layer, bool BKE_layer_collection_has_layer_collection(struct LayerCollection *lc_parent, struct LayerCollection *lc_child); +/** + * Update after toggling visibility of an object base. + */ void BKE_base_set_visible(struct Scene *scene, struct ViewLayer *view_layer, struct Base *base, bool extend); bool BKE_base_is_visible(const struct View3D *v3d, const struct Base *base); bool BKE_object_is_visible_in_viewport(const struct View3D *v3d, const struct Object *ob); +/** + * Isolate the collection - hide all other collections but this one. + * Make sure to show all the direct parents and all children of the layer collection as well. + * When extending we simply show the collections and its direct family. + * + * If the collection or any of its parents is disabled, make it enabled. + * Don't change the children disable state though. + */ void BKE_layer_collection_isolate_global(struct Scene *scene, struct ViewLayer *view_layer, struct LayerCollection *lc, bool extend); +/** + * Isolate the collection locally + * + * Same as #BKE_layer_collection_isolate_local but for a viewport + */ void BKE_layer_collection_isolate_local(struct ViewLayer *view_layer, const struct View3D *v3d, struct LayerCollection *lc, bool extend); +/** + * Hide/show all the elements of a collection. + * Don't change the collection children enable/disable state, + * but it may change it for the collection itself. + */ void BKE_layer_collection_set_visible(struct ViewLayer *view_layer, struct LayerCollection *lc, - const bool visible, - const bool hierarchy); -void BKE_layer_collection_set_flag(struct LayerCollection *lc, const int flag, const bool value); + bool visible, + bool hierarchy); +void BKE_layer_collection_set_flag(struct LayerCollection *lc, int flag, bool value); -/* evaluation */ +/* Evaluation. */ +/** + * Applies object's restrict flags on top of flags coming from the collection + * and stores those in `base->flag`. #BASE_VISIBLE_DEPSGRAPH ignores viewport flags visibility + * (i.e., restriction and local collection). + */ void BKE_base_eval_flags(struct Base *base); void BKE_layer_eval_view_layer_indexed(struct Depsgraph *depsgraph, @@ -328,7 +441,7 @@ void BKE_view_layer_visible_bases_iterator_end(BLI_Iterator *iter); { \ Object *_instance; \ Base *_base; \ - for (_base = (view_layer)->object_bases.first; _base; _base = _base->next) { \ + for (_base = (Base *)(view_layer)->object_bases.first; _base; _base = _base->next) { \ _instance = _base->object; #define FOREACH_OBJECT_END \ @@ -380,6 +493,13 @@ struct Object **BKE_view_layer_array_selected_objects_params( uint *r_len, const struct ObjectsInViewLayerParams *params); +/** + * Use this in rare cases we need to detect a pair of objects (active, selected). + * This returns the other non-active selected object. + * + * Returns NULL with it finds multiple other selected objects + * as behavior in this case would be random from the user perspective. + */ struct Object *BKE_view_layer_non_active_selected_object(struct ViewLayer *view_layer, const struct View3D *v3d); @@ -451,9 +571,20 @@ bool BKE_view_layer_filter_edit_mesh_has_edges(const struct Object *ob, void *us struct ViewLayerAOV *BKE_view_layer_add_aov(struct ViewLayer *view_layer); void BKE_view_layer_remove_aov(struct ViewLayer *view_layer, struct ViewLayerAOV *aov); void BKE_view_layer_set_active_aov(struct ViewLayer *view_layer, struct ViewLayerAOV *aov); +/** + * Update the naming and conflicts of the AOVs. + * + * Name must be unique between all AOVs. + * Conflicts with render passes will show a conflict icon. Reason is that switching a render + * engine or activating a render pass could lead to other conflicts that wouldn't be that clear + * for the user. + */ void BKE_view_layer_verify_aov(struct RenderEngine *engine, struct Scene *scene, struct ViewLayer *view_layer); +/** + * Check if the given view layer has at least one valid AOV. + */ bool BKE_view_layer_has_valid_aov(struct ViewLayer *view_layer); struct ViewLayer *BKE_view_layer_find_with_aov(struct Scene *scene, struct ViewLayerAOV *view_layer_aov); diff --git a/source/blender/blenkernel/BKE_lib_id.h b/source/blender/blenkernel/BKE_lib_id.h index 36f57209e33..ebd35cad965 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" { @@ -61,22 +62,66 @@ struct PointerRNA; struct PropertyRNA; struct bContext; +/** + * Get allocation size of a given data-block type and optionally allocation name. + */ size_t BKE_libblock_get_alloc_info(short type, const char **name); +/** + * Allocates and returns memory of the right size for the specified block type, + * initialized to zero. + */ void *BKE_libblock_alloc_notest(short type) ATTR_WARN_UNUSED_RESULT; -void *BKE_libblock_alloc(struct Main *bmain, short type, const char *name, const int flag) +/** + * Allocates and returns a block of the specified type, with the specified name + * (adjusted as necessary to ensure uniqueness), and appended to the specified list. + * The user count is set to 1, all other content (apart from name and links) being + * initialized to zero. + */ +void *BKE_libblock_alloc(struct Main *bmain, short type, const char *name, int flag) ATTR_WARN_UNUSED_RESULT; +/** + * Initialize an ID of given type, such that it has valid 'empty' data. + * ID is assumed to be just calloc'ed. + */ void BKE_libblock_init_empty(struct ID *id) ATTR_NONNULL(1); /* *** ID's session_uuid management. *** */ -/* When an ID's uuid is of that value, it is unset/invalid (e.g. for runtime IDs, etc.). */ +/** + * When an ID's uuid is of that value, it is unset/invalid (e.g. for runtime IDs, etc.). + */ #define MAIN_ID_SESSION_UUID_UNSET 0 +/** + * Generate a session-wise uuid for the given \a id. + * + * \note "session-wise" here means while editing a given .blend file. Once a new .blend file is + * loaded or created, undo history is cleared/reset, and so is the uuid counter. + */ void BKE_lib_libblock_session_uuid_ensure(struct ID *id); +/** + * Re-generate a new session-wise uuid for the given \a id. + * + * \warning This has a few very specific use-cases, no other usage is expected currently: + * - To handle UI-related data-blocks that are kept across new file reading, when we do keep + * existing UI. + * - For IDs that are made local without needing any copying. + */ void BKE_lib_libblock_session_uuid_renew(struct ID *id); -void *BKE_id_new(struct Main *bmain, const short type, const char *name); -void *BKE_id_new_nomain(const short type, const char *name); +/** + * Generic helper to create a new empty data-block of given type in given \a bmain database. + * + * \param name: can be NULL, in which case we get default name for this ID type. + */ +void *BKE_id_new(struct Main *bmain, short type, const char *name); +/** + * Generic helper to create a new temporary empty data-block of given type, + * *outside* of any Main database. + * + * \param name: can be NULL, in which case we get default name for this ID type. + */ +void *BKE_id_new_nomain(short type, const char *name); /** * New ID creation/copying options. @@ -122,6 +167,9 @@ enum { LIB_ID_COPY_CD_REFERENCE = 1 << 20, /** Do not copy id->override_library, used by ID datablock override routines. */ LIB_ID_COPY_NO_LIB_OVERRIDE = 1 << 21, + /** When copying local sub-data (like constraints or modifiers), do not set their "library + * override local data" flag. */ + LIB_ID_COPY_NO_LIB_OVERRIDE_LOCAL_DATA_FLAG = 1 << 22, /* *** XXX Hackish/not-so-nice specific behaviors needed for some corner cases. *** */ /* *** Ideally we should not have those, but we need them for now... *** */ @@ -133,6 +181,11 @@ 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... */ @@ -149,17 +202,27 @@ enum { void BKE_libblock_copy_ex(struct Main *bmain, const struct ID *id, struct ID **r_newid, - const int orig_flag); + int orig_flag); +/** + * Used everywhere in blenkernel. + */ void *BKE_libblock_copy(struct Main *bmain, const struct ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); +/** + * Sets the name of a block to name, suitably adjusted for uniqueness. + */ void BKE_libblock_rename(struct Main *bmain, struct ID *id, const char *name) ATTR_NONNULL(); +/** + * Use after setting the ID's name + * When name exists: call 'new_id' + */ void BLI_libblock_ensure_unique_name(struct Main *bmain, const char *name) ATTR_NONNULL(); struct ID *BKE_libblock_find_name(struct Main *bmain, - const short type, + 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. @@ -176,6 +239,8 @@ typedef enum eLibIDDuplicateFlags { 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. @@ -202,20 +267,73 @@ enum { LIB_ID_FREE_NO_UI_USER = 1 << 9, }; -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_datablock(struct ID *id, int flag) ATTR_NONNULL(); +void BKE_libblock_free_data(struct ID *id, bool do_id_user) ATTR_NONNULL(); +/** + * 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(struct ID *id); -void BKE_id_free_ex(struct Main *bmain, void *idv, int flag, const bool use_flag_from_idtag); +/** + * Complete ID freeing, extended version for corner cases. + * Can override default (and safe!) freeing process, to gain some speed up. + * + * At that point, given id is assumed to not be used by any other data-block already + * (might not be actually true, in case e.g. several inter-related IDs get freed together...). + * However, they might still be using (referencing) other IDs, this code takes care of it if + * #LIB_TAG_NO_USER_REFCOUNT is not defined. + * + * \param bmain: #Main database containing the freed #ID, + * can be NULL in case it's a temp ID outside of any #Main. + * \param idv: Pointer to ID to be freed. + * \param flag: Set of \a LIB_ID_FREE_... flags controlling/overriding usual freeing process, + * 0 to get default safe behavior. + * \param use_flag_from_idtag: Still use freeing info flags from given #ID datablock, + * even if some overriding ones are passed in \a flag parameter. + */ +void BKE_id_free_ex(struct Main *bmain, void *idv, int flag, bool use_flag_from_idtag); +/** + * Complete ID freeing, should be usable in most cases (even for out-of-Main IDs). + * + * See #BKE_id_free_ex description for full details. + * + * \param bmain: Main database containing the freed ID, + * can be NULL in case it's a temp ID outside of any Main. + * \param idv: Pointer to ID to be freed. + */ void BKE_id_free(struct Main *bmain, void *idv); +/** + * Not really a freeing function by itself, + * it decrements usercount of given id, and only frees it if it reaches 0. + */ void BKE_id_free_us(struct Main *bmain, void *idv) ATTR_NONNULL(); +/** + * Properly delete a single ID from given \a bmain database. + */ void BKE_id_delete(struct Main *bmain, void *idv) ATTR_NONNULL(); +/** + * Properly delete all IDs tagged with \a LIB_TAG_DOIT, in given \a bmain database. + * + * This is more efficient than calling #BKE_id_delete repetitively on a large set of IDs + * (several times faster when deleting most of the IDs at once). + * + * \warning Considered experimental for now, seems to be working OK but this is + * risky code in a complicated area. + * \return Number of deleted datablocks. + */ size_t BKE_id_multi_tagged_delete(struct Main *bmain) ATTR_NONNULL(); +/** + * Add a 'NO_MAIN' data-block to given main (also sets usercounts of its IDs if needed). + */ void BKE_libblock_management_main_add(struct Main *bmain, void *idv); +/** Remove a data-block from given main (set it to 'NO_MAIN' status). */ void BKE_libblock_management_main_remove(struct Main *bmain, void *idv); void BKE_libblock_management_usercounts_set(struct Main *bmain, void *idv); @@ -223,10 +341,23 @@ void BKE_libblock_management_usercounts_clear(struct Main *bmain, void *idv); void id_lib_extern(struct ID *id); void id_lib_indirect_weak_link(struct ID *id); +/** + * Ensure we have a real user + * + * \note Now that we have flags, we could get rid of the 'fake_user' special case, + * flags are enough to ensure we always have a real user. + * However, #ID_REAL_USERS is used in several places outside of core lib.c, + * so think we can wait later to make this change. + */ void id_us_ensure_real(struct ID *id); void id_us_clear_real(struct ID *id); +/** + * Same as \a id_us_plus, but does not handle lib indirect -> extern. + * Only used by readfile.c so far, but simpler/safer to keep it here nonetheless. + */ void id_us_plus_no_lib(struct ID *id); void id_us_plus(struct ID *id); +/* decrements the user count for *id. */ void id_us_min(struct ID *id); void id_fake_user_set(struct ID *id); void id_fake_user_clear(struct ID *id); @@ -242,84 +373,250 @@ enum { /** 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 int flags); +/** + * Helper to decide whether given `id` can be directly made local, or needs to be copied. + * `r_force_local` and `r_force_copy` cannot be true together. But both can be false, in case no + * action should be performed. + * + * \note low-level helper to de-duplicate logic between `BKE_lib_id_make_local_generic` and the + * specific corner-cases implementations needed for objects and brushes. + */ +void BKE_lib_id_make_local_generic_action_define( + struct Main *bmain, struct ID *id, int flags, bool *r_force_local, bool *r_force_copy); +/** + * Generic 'make local' function, works for most of data-block types. + */ +void BKE_lib_id_make_local_generic(struct Main *bmain, struct ID *id, int flags); +/** + * Calls the appropriate make_local method for the block, unless test is set. + * + * \note Always set #ID.newid pointer in case it gets duplicated. + * + * \param flags: Special flag used when making a whole library's content local, + * it needs specific handling. + * \return true is the ID has successfully been made local. + */ +bool BKE_lib_id_make_local(struct Main *bmain, struct ID *id, int flags); +/** + * \note Does *not* set #ID.newid pointer. + */ bool id_single_user(struct bContext *C, struct ID *id, struct PointerRNA *ptr, struct PropertyRNA *prop); bool BKE_id_copy_is_allowed(const struct ID *id); +/** + * Invokes the appropriate copy method for the block and returns the result in + * #ID.newid, unless test. Returns true if the block can be copied. + */ struct ID *BKE_id_copy(struct Main *bmain, const struct ID *id); -struct ID *BKE_id_copy_ex(struct Main *bmain, - const struct ID *id, - struct ID **r_newid, - const int flag); +/** + * Generic entry point for copying a data-block (new API). + * + * \note Copy is generally only affecting the given data-block + * (no ID used by copied one will be affected, besides user-count). + * + * There are exceptions though: + * - Embedded IDs (root node trees and master collections) are always copied with their owner. + * - If #LIB_ID_COPY_ACTIONS is defined, actions used by animdata will be duplicated. + * - If #LIB_ID_COPY_SHAPEKEY is defined, shape-keys will be duplicated. + * - If #LIB_ID_CREATE_LOCAL is defined, root node trees will be deep-duplicated recursively. + * + * \note User-count of new copy is always set to 1. + * + * \param bmain: Main database, may be NULL only if LIB_ID_CREATE_NO_MAIN is specified. + * \param id: Source data-block. + * \param r_newid: Pointer to new (copied) ID pointer, may be NULL. + * Used to allow copying into already allocated memory. + * \param flag: Set of copy options, see `DNA_ID.h` enum for details + * (leave to zero for default, full copy). + * \return NULL when copying that ID type is not supported, the new copy otherwise. + */ +struct ID *BKE_id_copy_ex(struct Main *bmain, const struct ID *id, struct ID **r_newid, int flag); +/** + * Invokes the appropriate copy method for the block and returns the result in + * newid, unless test. Returns true if the block can be copied. + */ struct ID *BKE_id_copy_for_duplicate(struct Main *bmain, struct ID *id, - const uint duplicate_flags); + uint duplicate_flags, + int copy_flags); +/** + * Does a mere memory swap over the whole IDs data (including type-specific memory). + * \note Most internal ID data itself is not swapped (only IDProperties are). + * + * \param bmain: May be NULL, in which case there will be no remapping of internal pointers to + * itself. + */ void BKE_lib_id_swap(struct Main *bmain, struct ID *id_a, struct ID *id_b); +/** + * Does a mere memory swap over the whole IDs data (including type-specific memory). + * \note All internal ID data itself is also swapped. + * + * \param bmain: May be NULL, in which case there will be no remapping of internal pointers to + * itself. + */ void BKE_lib_id_swap_full(struct Main *bmain, struct ID *id_a, struct ID *id_b); +/** + * Sort given \a id into given \a lb list, using case-insensitive comparison of the id names. + * + * \note All other IDs beside given one are assumed already properly sorted in the list. + * + * \param id_sorting_hint: Ignored if NULL. Otherwise, used to check if we can insert \a id + * immediately before or after that pointer. It must always be into given \a lb list. + */ 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); +/** + * 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(struct Main *bmain, struct ID *id, int flags); +/** + * Ensures given ID has a unique name in given listbase. + * + * 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 + * (otherwise, just ensure that it is properly sorted). + * + * \return true if a new name had to be created. + */ 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); + bool do_linked_data) ATTR_NONNULL(1, 2); +/** + * 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. + */ +void BKE_lib_id_clear_library_data(struct Main *bmain, struct ID *id, int flags); -/* Affect whole Main database. */ -void BKE_main_id_tag_idcode(struct Main *mainvar, - const short type, - const int tag, - const bool value); -void BKE_main_id_tag_listbase(struct ListBase *lb, const int tag, const bool value); -void BKE_main_id_tag_all(struct Main *mainvar, const int tag, const bool value); +/** + * Clear or set given tags for all ids of given type in `bmain` (runtime tags). + * + * \note Affect whole Main database. + */ +void BKE_main_id_tag_idcode(struct Main *mainvar, short type, int tag, bool value); +/** + * Clear or set given tags for all ids in listbase (runtime tags). + */ +void BKE_main_id_tag_listbase(struct ListBase *lb, int tag, bool value); +/** + * Clear or set given tags for all ids in bmain (runtime tags). + */ +void BKE_main_id_tag_all(struct Main *mainvar, int tag, bool value); -void BKE_main_id_flag_listbase(struct ListBase *lb, const int flag, const bool value); -void BKE_main_id_flag_all(struct Main *bmain, const int flag, const bool value); +/** + * Clear or set given flags for all ids in listbase (persistent flags). + */ +void BKE_main_id_flag_listbase(struct ListBase *lb, int flag, bool value); +/** + * Clear or set given flags for all ids in bmain (persistent flags). + */ +void BKE_main_id_flag_all(struct Main *bmain, int flag, bool value); +/** + * Next to indirect usage in `readfile.c/writefile.c` also in `editobject.c`, `scene.c`. + */ void BKE_main_id_newptr_and_tag_clear(struct Main *bmain); -void BKE_main_id_refcount_recompute(struct Main *bmain, const bool do_linked_only); +void BKE_main_id_refcount_recompute(struct Main *bmain, bool do_linked_only); void BKE_main_lib_objects_recalc_all(struct Main *bmain); -/* Only for repairing files via versioning, avoid for general use. */ +/** + * Only for repairing files via versioning, avoid for general use. + */ void BKE_main_id_repair_duplicate_names_listbase(struct ListBase *lb); #define MAX_ID_FULL_NAME (64 + 64 + 3 + 1) /* 64 is MAX_ID_NAME - 2 */ #define MAX_ID_FULL_NAME_UI (MAX_ID_FULL_NAME + 3) /* Adds 'keycode' two letters at beginning. */ +/** + * Generate full name of the data-block (without ID code, but with library if any). + * + * \note Result is unique to a given ID type in a given Main database. + * + * \param name: An allocated string of minimal length #MAX_ID_FULL_NAME, + * will be filled with generated string. + * \param separator_char: Character to use for separating name and library name. + * Can be 0 to use default (' '). + */ void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const struct ID *id, char separator_char); +/** + * Generate full name of the data-block (without ID code, but with library if any), + * with a 2 to 3 character prefix prepended indicating whether it comes from a library, + * is overriding, has a fake or no user, etc. + * + * \note Result is unique to a given ID type in a given Main database. + * + * \param name: An allocated string of minimal length #MAX_ID_FULL_NAME_UI, + * will be filled with generated string. + * \param separator_char: Character to use for separating name and library name. + * Can be 0 to use default (' '). + * \param r_prefix_len: The length of the prefix added. + */ void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI], const struct ID *id, - const bool add_lib_hint, + bool add_lib_hint, char separator_char, int *r_prefix_len); +/** + * Generate a concatenation of ID name (including two-chars type code) and its lib name, if any. + * + * \return A unique allocated string key for any ID in the whole Main database. + */ char *BKE_id_to_unique_string_key(const struct ID *id); +/** + * Make linked data-blocks local. + * + * \param bmain: Almost certainly global main. + * \param lib: If not NULL, only make local data-blocks from this library. + * \param untagged_only: If true, only make local data-blocks not tagged with + * #LIB_TAG_PRE_EXISTING. + * \param set_fake: If true, set fake user on all localized data-blocks + * (except group and objects ones). + */ void BKE_library_make_local(struct Main *bmain, const struct Library *lib, struct GHash *old_to_new_ids, - const bool untagged_only, - const bool set_fake); + bool untagged_only, + bool set_fake); void BKE_id_tag_set_atomic(struct ID *id, int tag); void BKE_id_tag_clear_atomic(struct ID *id, int tag); +/** + * Check that given ID pointer actually is in G_MAIN. + * Main intended use is for debug asserts in places we cannot easily get rid of #G_Main. + */ bool BKE_id_is_in_global_main(struct ID *id); bool BKE_id_can_be_asset(const struct ID *id); +/** + * Returns ordered list of data-blocks for display in the UI. + * Result is list of #LinkData of IDs that must be freed. + */ void BKE_id_ordered_list(struct ListBase *ordered_lb, const struct ListBase *lb); +/** + * Reorder ID in the list, before or after the "relative" ID. + */ void BKE_id_reorder(const struct ListBase *lb, struct ID *id, struct ID *relative, bool after); void BKE_id_blend_write(struct BlendWriter *writer, struct ID *id); @@ -327,6 +624,12 @@ void BKE_id_blend_write(struct BlendWriter *writer, struct ID *id); #define IS_TAGGED(_id) ((_id) && (((ID *)_id)->tag & LIB_TAG_DOIT)) /* lib_id_eval.c */ + +/** + * Copy relatives parameters, from `id` to `id_cow`. + * Use handle the #ID_RECALC_PARAMETERS tag. + * \note Keep in sync with #ID_TYPE_SUPPORTS_PARAMS_WITHOUT_COW. + */ void BKE_id_eval_properties_copy(struct ID *id_cow, struct ID *id); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_lib_override.h b/source/blender/blenkernel/BKE_lib_override.h index b94a1b33606..30e75259967 100644 --- a/source/blender/blenkernel/BKE_lib_override.h +++ b/source/blender/blenkernel/BKE_lib_override.h @@ -57,85 +57,226 @@ struct ReportList; struct Scene; struct ViewLayer; +/** + * Initialize empty overriding of \a reference_id by \a local_id. + */ struct IDOverrideLibrary *BKE_lib_override_library_init(struct ID *local_id, struct ID *reference_id); -void BKE_lib_override_library_copy(struct ID *dst_id, - const struct ID *src_id, - const bool do_full_copy); -void BKE_lib_override_library_clear(struct IDOverrideLibrary *override, const bool do_id_user); -void BKE_lib_override_library_free(struct IDOverrideLibrary **override, const bool do_id_user); +/** + * Shallow or deep copy of a whole override from \a src_id to \a dst_id. + */ +void BKE_lib_override_library_copy(struct ID *dst_id, const struct ID *src_id, bool do_full_copy); +/** + * Clear any overriding data from given \a override. + */ +void BKE_lib_override_library_clear(struct IDOverrideLibrary *override, bool do_id_user); +/** + * Free given \a override. + */ +void BKE_lib_override_library_free(struct IDOverrideLibrary **override, bool do_id_user); +/** + * Check if given ID has some override rules that actually indicate the user edited it. + */ bool BKE_lib_override_library_is_user_edited(struct ID *id); +/** + * Create an overridden local copy of linked reference. + * + * \note This function is very basic, low-level. It does not consider any hierarchical dependency, + * and also prevents any automatic re-sync of this local override. + */ struct ID *BKE_lib_override_library_create_from_id(struct Main *bmain, struct ID *reference_id, - const bool do_tagged_remap); + bool do_tagged_remap); +/** + * Create overridden local copies of all tagged data-blocks in given Main. + * + * \note Set `id->newid` of overridden libs with newly created overrides, + * caller is responsible to clean those pointers before/after usage as needed. + * + * \note By default, it will only remap newly created local overriding data-blocks between + * themselves, to avoid 'enforcing' those overrides into all other usages of the linked data in + * 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 + * (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. + * + * \return \a true on success, \a false otherwise. + */ bool BKE_lib_override_library_create_from_tag(struct Main *bmain, const struct Library *reference_library, - const bool do_no_main); + bool do_no_main); +/** + * Advanced 'smart' function to create fully functional overrides. + * + * \note Currently it only does special things if given \a id_root is an object or collection, more + * specific behaviors may be added in the future for other ID types. + * + * \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. + * \return true if override was successfully created. + */ bool BKE_lib_override_library_create(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct ID *id_root, struct ID *id_reference, struct ID **r_id_root_override); +/** + * Create a library override template. + */ bool BKE_lib_override_library_template_create(struct ID *id); +/** + * Convert a given proxy object into a library override. + * + * \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(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct Object *ob_proxy); +/** + * Convert all proxy objects into library overrides. + * + * \note Only affects local proxies, linked ones are not affected. + */ void BKE_lib_override_library_main_proxy_convert(struct Main *bmain, struct BlendFileReadReport *reports); +/** + * Advanced 'smart' function to resync, re-create fully functional overrides up-to-date with linked + * data, from an existing override hierarchy. + * + * \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 liboverride ID to resync from. + * \return true if override was successfully resynced. + */ bool BKE_lib_override_library_resync(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct ID *id_root, struct Collection *override_resync_residual_storage, - const bool do_hierarchy_enforce, - const bool do_post_process, + bool do_hierarchy_enforce, struct BlendFileReadReport *reports); +/** + * Detect and handle required resync of overrides data, when relations between reference linked IDs + * have changed. + * + * This is a fairly complex and costly operation, typically it should be called after + * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases. + * + * This function will first detect the remaining cases requiring a resync (namely, either when an + * existing linked ID that did not require to be overridden before now would be, or when new IDs + * are added to the hierarchy). + * + * 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(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, struct BlendFileReadReport *reports); +/** + * Advanced 'smart' function to delete library overrides (including their existing override + * hierarchy) and remap their usages to their linked reference IDs. + * + * \note All IDs tagged with #LIB_TAG_DOIT will be deleted. + * + * \param id_root: The root liboverride ID to delete. + */ void BKE_lib_override_library_delete(struct Main *bmain, struct ID *id_root); +/** + * Make given ID fully local. + * + * \note Only differs from lower-level #BKE_lib_override_library_free in infamous embedded ID + * cases. + */ void BKE_lib_override_library_make_local(struct ID *id); +/** + * Find override property from given RNA path, if it exists. + */ struct IDOverrideLibraryProperty *BKE_lib_override_library_property_find( struct IDOverrideLibrary *override, const char *rna_path); +/** + * Find override property from given RNA path, or create it if it does not exist. + */ struct IDOverrideLibraryProperty *BKE_lib_override_library_property_get( struct IDOverrideLibrary *override, const char *rna_path, bool *r_created); +/** + * Remove and free given \a override_property from given ID \a override. + */ void BKE_lib_override_library_property_delete(struct IDOverrideLibrary *override, struct IDOverrideLibraryProperty *override_property); +/** + * Get the RNA-property matching the \a library_prop override property. Used for UI to query + * additional data about the overridden property (e.g. UI name). + * + * \param idpoin: Pointer to the override ID. + * \param library_prop: The library override property to find the matching RNA property for. + */ bool BKE_lib_override_rna_property_find(struct PointerRNA *idpoin, const struct IDOverrideLibraryProperty *library_prop, struct PointerRNA *r_override_poin, struct PropertyRNA **r_override_prop); +/** + * Find override property operation from given sub-item(s), if it exists. + */ struct IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_find( struct IDOverrideLibraryProperty *override_property, const char *subitem_refname, const char *subitem_locname, - const int subitem_refindex, - const int subitem_locindex, - const bool strict, + int subitem_refindex, + int subitem_locindex, + bool strict, bool *r_strict); +/** + * Find override property operation from given sub-item(s), or create it if it does not exist. + */ struct IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_get( struct IDOverrideLibraryProperty *override_property, - const short operation, + short operation, const char *subitem_refname, const char *subitem_locname, - const int subitem_refindex, - const int subitem_locindex, - const bool strict, + int subitem_refindex, + int subitem_locindex, + bool strict, bool *r_strict, bool *r_created); +/** + * Remove and free given \a override_property_operation from given ID \a override_property. + */ void BKE_lib_override_library_property_operation_delete( struct IDOverrideLibraryProperty *override_property, struct IDOverrideLibraryPropertyOperation *override_property_operation); +/** + * Validate that required data for a given operation are available. + */ bool BKE_lib_override_library_property_operation_operands_validate( struct IDOverrideLibraryPropertyOperation *override_property_operation, struct PointerRNA *ptr_dst, @@ -145,34 +286,109 @@ bool BKE_lib_override_library_property_operation_operands_validate( struct PropertyRNA *prop_src, struct PropertyRNA *prop_storage); +/** + * Check against potential \a bmain. + */ void BKE_lib_override_library_validate(struct Main *bmain, struct ID *id, struct ReportList *reports); +/** + * Check against potential \a bmain. + */ void BKE_lib_override_library_main_validate(struct Main *bmain, struct ReportList *reports); +/** + * Check that status of local data-block is still valid against current reference one. + * + * It means that all overridable, but not overridden, properties' local values must be equal to + * reference ones. Clears #LIB_TAG_OVERRIDE_OK if they do not. + * + * This is typically used to detect whether some property has been changed in local and a new + * #IDOverrideProperty (of #IDOverridePropertyOperation) has to be added. + * + * \return true if status is OK, false otherwise. + */ bool BKE_lib_override_library_status_check_local(struct Main *bmain, struct ID *local); +/** + * Check that status of reference data-block is still valid against current local one. + * + * It means that all non-overridden properties' local values must be equal to reference ones. + * Clears LIB_TAG_OVERRIDE_OK if they do not. + * + * This is typically used to detect whether some reference has changed and local + * needs to be updated against it. + * + * \return true if status is OK, false otherwise. + */ bool BKE_lib_override_library_status_check_reference(struct Main *bmain, struct ID *local); +/** + * Compare local and reference data-blocks and create new override operations as needed, + * or reset to reference values if overriding is not allowed. + * + * \note Defining override operations is only mandatory before saving a `.blend` file on disk + * (not for undo!). + * Knowing that info at runtime is only useful for UI/UX feedback. + * + * \note This is by far the biggest operation (the more time-consuming) of the three so far, + * since it has to go over all properties in depth (all overridable ones at least). + * Generating differential values and applying overrides are much cheaper. + * + * \return true if any library operation was created. + */ bool BKE_lib_override_library_operations_create(struct Main *bmain, struct ID *local); -bool BKE_lib_override_library_main_operations_create(struct Main *bmain, const bool force_auto); +/** + * Check all overrides from given \a bmain and create/update overriding operations as needed. + */ +bool BKE_lib_override_library_main_operations_create(struct Main *bmain, bool force_auto); +/** + * Reset all overrides in given \a id_root, while preserving ID relations. + */ void BKE_lib_override_library_id_reset(struct Main *bmain, struct ID *id_root); +/** + * Reset all overrides in given \a id_root and its dependencies, while preserving ID relations. + */ void BKE_lib_override_library_id_hierarchy_reset(struct Main *bmain, struct ID *id_root); +/** + * Set or clear given tag in all operations in that override property data. + */ void BKE_lib_override_library_operations_tag(struct IDOverrideLibraryProperty *override_property, - const short tag, - const bool do_set); + short tag, + bool do_set); +/** + * Set or clear given tag in all properties and operations in that override data. + */ void BKE_lib_override_library_properties_tag(struct IDOverrideLibrary *override, - const short tag, - const bool do_set); -void BKE_lib_override_library_main_tag(struct Main *bmain, const short tag, const bool do_set); + short tag, + bool do_set); +/** + * Set or clear given tag in all properties and operations in that Main's ID override data. + */ +void BKE_lib_override_library_main_tag(struct Main *bmain, short tag, bool do_set); +/** + * Remove all tagged-as-unused properties and operations from that ID override data. + */ void BKE_lib_override_library_id_unused_cleanup(struct ID *local); +/** + * Remove all tagged-as-unused properties and operations from that Main's ID override data. + */ void BKE_lib_override_library_main_unused_cleanup(struct Main *bmain); +/** + * Update given override from its reference (re-applying overridden properties). + */ void BKE_lib_override_library_update(struct Main *bmain, struct ID *local); +/** + * Update all overrides from given \a bmain. + */ void BKE_lib_override_library_main_update(struct Main *bmain); +/** + * 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); /* Storage (.blend file writing) part. */ @@ -180,9 +396,22 @@ bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID /* For now, we just use a temp main list. */ typedef struct Main OverrideLibraryStorage; +/** + * Initialize an override storage. + */ OverrideLibraryStorage *BKE_lib_override_library_operations_store_init(void); +/** + * Generate suitable 'write' data (this only affects differential override operations). + * + * Note that \a local ID is no more modified by this call, + * all extra data are stored in its temp \a storage_id copy. + */ struct ID *BKE_lib_override_library_operations_store_start( struct Main *bmain, OverrideLibraryStorage *override_storage, struct ID *local); +/** + * Restore given ID modified by #BKE_lib_override_library_operations_store_start, to its + * original state. + */ void BKE_lib_override_library_operations_store_end(OverrideLibraryStorage *override_storage, struct ID *local); void BKE_lib_override_library_operations_store_finalize(OverrideLibraryStorage *override_storage); diff --git a/source/blender/blenkernel/BKE_lib_query.h b/source/blender/blenkernel/BKE_lib_query.h index 9c49514e7b8..d853cb16b13 100644 --- a/source/blender/blenkernel/BKE_lib_query.h +++ b/source/blender/blenkernel/BKE_lib_query.h @@ -143,59 +143,144 @@ enum { typedef struct LibraryForeachIDData LibraryForeachIDData; -bool BKE_lib_query_foreachid_process(struct LibraryForeachIDData *data, +/** + * 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(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); int BKE_lib_query_foreachid_process_callback_flag_override(struct LibraryForeachIDData *data, - const int cb_flag, - const bool do_replace); + int cb_flag, + bool do_replace); #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) + +#define BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(_data, _func_call) \ + { \ + _func_call; \ + if (BKE_lib_query_foreachid_iter_stop((_data))) { \ return; \ } \ } \ ((void)0) -bool BKE_library_foreach_ID_embedded(struct LibraryForeachIDData *data, struct ID **id_pp); +/** + * Process embedded ID pointers (root node-trees, 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(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. */ +/** + * Loop over all of the ID's this data-block links to. + */ void BKE_library_foreach_ID_link( struct Main *bmain, struct ID *id, LibraryIDLinkCallback callback, void *user_data, int flag); -void BKE_library_update_ID_link_user(struct ID *id_dst, struct ID *id_src, const int cb_flag); +/** + * Re-usable function, use when replacing ID's. + */ +void BKE_library_update_ID_link_user(struct ID *id_dst, struct ID *id_src, int cb_flag); +/** + * Return the number of times given \a id_user uses/references \a id_used. + * + * \note This only checks for pointer references of an ID, shallow usages + * (like e.g. by RNA paths, as done for FCurves) are not detected at all. + * + * \param id_user: the ID which is supposed to use (reference) \a id_used. + * \param id_used: the ID which is supposed to be used (referenced) by \a id_user. + * \return the number of direct usages/references of \a id_used by \a id_user. + */ int BKE_library_ID_use_ID(struct ID *id_user, struct ID *id_used); -bool BKE_library_id_can_use_idtype(struct ID *id_owner, const short id_type_used); +/** + * Say whether given \a id_owner may use (in any way) a data-block of \a id_type_used. + * + * This is a 'simplified' abstract version of #BKE_library_foreach_ID_link() above, + * quite useful to reduce useless iterations in some cases. + */ +bool BKE_library_id_can_use_idtype(struct ID *id_owner, short id_type_used); +/** + * Check whether given ID is used locally (i.e. by another non-linked ID). + */ bool BKE_library_ID_is_locally_used(struct Main *bmain, void *idv); +/** + * Check whether given ID is used indirectly (i.e. by another linked ID). + */ bool BKE_library_ID_is_indirectly_used(struct Main *bmain, void *idv); +/** + * Combine #BKE_library_ID_is_locally_used() and #BKE_library_ID_is_indirectly_used() + * in a single call. + */ void BKE_library_ID_test_usages(struct Main *bmain, void *idv, bool *is_used_local, bool *is_used_linked); +/** + * Tag all unused IDs (a.k.a 'orphaned'). + * + * By default only tag IDs with `0` user count. + * If `do_tag_recursive` is set, it will check dependencies to detect all IDs that are not actually + * used in current file, including 'archipelagos` (i.e. set of IDs referencing each other in + * loops, but without any 'external' valid usages. + * + * 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. + */ void BKE_lib_query_unused_ids_tag(struct Main *bmain, - const int tag, - const bool do_local_ids, - const bool do_linked_ids, - const bool do_tag_recursive, + int tag, + bool do_local_ids, + bool do_linked_ids, + bool do_tag_recursive, int *r_num_tagged); -void BKE_library_unused_linked_data_set_tag(struct Main *bmain, const bool do_init_tag); +/** + * Detect orphaned linked data blocks (i.e. linked data not used (directly or indirectly) + * in any way by any local data), including complex cases like 'linked archipelagoes', i.e. + * linked data-blocks that use each other in loops, + * which prevents their deletion by 'basic' usage checks. + * + * \param do_init_tag: if \a true, all linked data are checked, if \a false, + * only linked data-blocks already tagged with #LIB_TAG_DOIT are checked. + */ +void BKE_library_unused_linked_data_set_tag(struct Main *bmain, bool do_init_tag); +/** + * Untag linked data blocks used by other untagged linked data-blocks. + * Used to detect data-blocks that we can forcefully make local + * (instead of copying them to later get rid of original): + * All data-blocks we want to make local are tagged by caller, + * after this function has ran caller knows data-blocks still tagged can directly be made local, + * since they are only used by other data-blocks that will also be made fully local. + */ void BKE_library_indirectly_used_data_tag_clear(struct Main *bmain); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_lib_remap.h b/source/blender/blenkernel/BKE_lib_remap.h index c70521f9593..cc970342fbb 100644 --- a/source/blender/blenkernel/BKE_lib_remap.h +++ b/source/blender/blenkernel/BKE_lib_remap.h @@ -38,6 +38,9 @@ extern "C" { #endif +struct ID; +struct IDRemapper; + /* BKE_libblock_free, delete are declared in BKE_lib_id.h for convenience. */ /* Also IDRemap->flag. */ @@ -89,38 +92,131 @@ 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, - * but makes things simpler for now. */ -void BKE_libblock_remap_locked(struct Main *bmain, - void *old_idv, - void *new_idv, - const short remap_flags) ATTR_NONNULL(1, 2); -void BKE_libblock_remap(struct Main *bmain, void *old_idv, void *new_idv, const short remap_flags) +/** + * Replace all references in given Main using the given \a mappings + * + * \note Is preferred over BKE_libblock_remap_locked due to performance. + */ +void BKE_libblock_remap_multiple_locked(struct Main *bmain, + const struct IDRemapper *mappings, + const short remap_flags); + +void BKE_libblock_remap_multiple(struct Main *bmain, + const struct IDRemapper *mappings, + const short remap_flags); + +/** + * Replace all references in given Main to \a old_id by \a new_id + * (if \a new_id is NULL, it unlinks \a old_id). + * + * \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, void *new_idv, short remap_flags) + ATTR_NONNULL(1, 2); +void BKE_libblock_remap(struct Main *bmain, void *old_idv, void *new_idv, short remap_flags) ATTR_NONNULL(1, 2); +/** + * Unlink given \a id from given \a bmain + * (does not touch to indirect, i.e. library, usages of the ID). + * + * \param do_flag_never_null: If true, all IDs using \a idv in a 'non-NULL' way are flagged by + * #LIB_TAG_DOIT flag (quite obviously, 'non-NULL' usages can never be unlinked by this function). + */ void BKE_libblock_unlink(struct Main *bmain, void *idv, - const bool do_flag_never_null, - const bool do_skip_indirect) ATTR_NONNULL(); + bool do_flag_never_null, + bool do_skip_indirect) ATTR_NONNULL(); +/** + * Similar to libblock_remap, but only affects IDs used by given \a idv ID. + * + * \param old_idv: Unlike BKE_libblock_remap, can be NULL, + * in which case all ID usages by given \a idv will be cleared. + */ void BKE_libblock_relink_ex(struct Main *bmain, void *idv, void *old_idv, void *new_idv, - const short remap_flags) ATTR_NONNULL(1, 2); + short remap_flags) ATTR_NONNULL(1, 2); -void BKE_libblock_relink_to_newid(struct ID *id) ATTR_NONNULL(); -void BKE_libblock_relink_to_newid_new(struct Main *bmain, struct ID *id) ATTR_NONNULL(); +/** + * 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(struct Main *bmain, struct ID *id, 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 *); +typedef void (*BKE_library_remap_editor_id_reference_cb)(const struct IDRemapper *mappings); void BKE_library_callback_free_notifier_reference_set(BKE_library_free_notifier_reference_cb func); void BKE_library_callback_remap_editor_id_reference_set( BKE_library_remap_editor_id_reference_cb func); +/* IDRemapper */ +struct IDRemapper; +typedef enum IDRemapperApplyResult { + /** No remapping rules available for the source. */ + ID_REMAP_RESULT_SOURCE_UNAVAILABLE, + /** Source isn't mappable (e.g. NULL). */ + ID_REMAP_RESULT_SOURCE_NOT_MAPPABLE, + /** Source has been remapped to a new pointer. */ + ID_REMAP_RESULT_SOURCE_REMAPPED, + /** Source has been set to NULL. */ + ID_REMAP_RESULT_SOURCE_UNASSIGNED, +} IDRemapperApplyResult; + +typedef enum IDRemapperApplyOptions { + ID_REMAP_APPLY_UPDATE_REFCOUNT = (1 << 0), + ID_REMAP_APPLY_ENSURE_REAL = (1 << 1), + + ID_REMAP_APPLY_DEFAULT = 0, +} IDRemapperApplyOptions; + +typedef void (*IDRemapperIterFunction)(struct ID *old_id, struct ID *new_id, void *user_data); + +/** + * Create a new ID Remapper. + * + * An ID remapper stores multiple remapping rules. + */ +struct IDRemapper *BKE_id_remapper_create(void); + +void BKE_id_remapper_clear(struct IDRemapper *id_remapper); +bool BKE_id_remapper_is_empty(const struct IDRemapper *id_remapper); +/** Free the given ID Remapper. */ +void BKE_id_remapper_free(struct IDRemapper *id_remapper); +/** Add a new remapping. */ +void BKE_id_remapper_add(struct IDRemapper *id_remapper, struct ID *old_id, struct ID *new_id); + +/** + * Apply a remapping. + * + * Update the id pointer stored in the given r_id_ptr if a remapping rule exists. + */ +IDRemapperApplyResult BKE_id_remapper_apply(const struct IDRemapper *id_remapper, + struct ID **r_id_ptr, + IDRemapperApplyOptions options); +bool BKE_id_remapper_has_mapping_for(const struct IDRemapper *id_remapper, uint64_t type_filter); +void BKE_id_remapper_iter(const struct IDRemapper *id_remapper, + IDRemapperIterFunction func, + void *user_data); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_lightprobe.h b/source/blender/blenkernel/BKE_lightprobe.h index 764914ee315..59c5d32c03e 100644 --- a/source/blender/blenkernel/BKE_lightprobe.h +++ b/source/blender/blenkernel/BKE_lightprobe.h @@ -31,7 +31,7 @@ extern "C" { struct LightProbe; struct Main; -void BKE_lightprobe_type_set(struct LightProbe *probe, const short lightprobe_type); +void BKE_lightprobe_type_set(struct LightProbe *probe, short lightprobe_type); void *BKE_lightprobe_add(struct Main *bmain, const char *name); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_linestyle.h b/source/blender/blenkernel/BKE_linestyle.h index 1236a96c8d9..94a5fde5468 100644 --- a/source/blender/blenkernel/BKE_linestyle.h +++ b/source/blender/blenkernel/BKE_linestyle.h @@ -60,16 +60,16 @@ LineStyleModifier *BKE_linestyle_geometry_modifier_add(FreestyleLineStyle *lines LineStyleModifier *BKE_linestyle_color_modifier_copy(FreestyleLineStyle *linestyle, const LineStyleModifier *m, - const int flag); + int flag); LineStyleModifier *BKE_linestyle_alpha_modifier_copy(FreestyleLineStyle *linestyle, const LineStyleModifier *m, - const int flag); + int flag); LineStyleModifier *BKE_linestyle_thickness_modifier_copy(FreestyleLineStyle *linestyle, const LineStyleModifier *m, - const int flag); + int flag); LineStyleModifier *BKE_linestyle_geometry_modifier_copy(FreestyleLineStyle *linestyle, const LineStyleModifier *m, - const int flag); + int flag); int BKE_linestyle_color_modifier_remove(FreestyleLineStyle *linestyle, LineStyleModifier *modifier); @@ -80,6 +80,10 @@ int BKE_linestyle_thickness_modifier_remove(FreestyleLineStyle *linestyle, int BKE_linestyle_geometry_modifier_remove(FreestyleLineStyle *linestyle, LineStyleModifier *modifier); +/** + * Reinsert \a modifier in modifier list with an offset of \a direction. + * \return if position of \a modifier has changed. + */ bool BKE_linestyle_color_modifier_move(FreestyleLineStyle *linestyle, LineStyleModifier *modifier, int direction); @@ -97,7 +101,7 @@ void BKE_linestyle_modifier_list_color_ramps(FreestyleLineStyle *linestyle, List char *BKE_linestyle_path_to_color_ramp(FreestyleLineStyle *linestyle, struct ColorBand *color_ramp); -bool BKE_linestyle_use_textures(FreestyleLineStyle *linestyle, const bool use_shading_nodes); +bool BKE_linestyle_use_textures(FreestyleLineStyle *linestyle, bool use_shading_nodes); void BKE_linestyle_default_shader(const struct bContext *C, FreestyleLineStyle *linestyle); diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h index 93d5b5c5aa6..4c6eb31db4b 100644 --- a/source/blender/blenkernel/BKE_main.h +++ b/source/blender/blenkernel/BKE_main.h @@ -90,12 +90,12 @@ 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). @@ -116,12 +116,14 @@ enum { typedef struct Main { struct Main *next, *prev; - char name[1024]; /* 1024 = FILE_MAX */ + /** The file-path of this blend file, an empty string indicates an unsaved file. */ + char filepath[1024]; /* 1024 = FILE_MAX */ short versionfile, subversionfile; /* see BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION */ short minversionfile, minsubversionfile; uint64_t build_commit_timestamp; /* commit's timestamp from buildinfo */ char build_hash[16]; /* hash from buildinfo */ - char recovered; /* indicate the main->name (file) is the recovered one */ + /** Indicate the #Main.filepath (file) is the recovered one. */ + char recovered; /** All current ID's exist in the last memfile undo step. */ char is_memfile_undo_written; /** @@ -201,38 +203,97 @@ typedef struct Main { struct Main *BKE_main_new(void); void BKE_main_free(struct Main *mainvar); +/** + * Check whether given `bmain` is empty or contains some IDs. + */ +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); +/** Generate the mappings between used IDs and their users, and vice-versa. */ +void BKE_main_relations_create(struct Main *bmain, short flag); void BKE_main_relations_free(struct Main *bmain); -void BKE_main_relations_tag_set(struct Main *bmain, - const MainIDRelationsEntryTags tag, - const bool value); +/** Set or clear given `tag` in all relation entries of given `bmain`. */ +void BKE_main_relations_tag_set(struct Main *bmain, eMainIDRelationsEntryTags tag, bool value); +/** + * Create a #GSet storing all IDs present in given \a bmain, by their pointers. + * + * \param gset: If not NULL, given GSet will be extended with IDs from given \a bmain, + * instead of creating a new one. + */ 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. - */ +/* Temporary runtime API to allow re-using local (already appended) + * IDs instead of appending a new copy again. */ +/** + * 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`. + */ struct GHash *BKE_main_library_weak_reference_create(struct Main *bmain) ATTR_NONNULL(); +/** + * Destroy the data generated by #BKE_main_library_weak_reference_create. + */ void BKE_main_library_weak_reference_destroy(struct GHash *library_weak_reference_mapping) ATTR_NONNULL(); +/** + * 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_filepath: 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. + */ 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(); +/** + * 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_filepath: 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(struct GHash *library_weak_reference_mapping, const char *library_filepath, const char *library_id_name, struct ID *new_id) ATTR_NONNULL(); +/** + * 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_filepath: 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(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(); +/** + * 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_filepath: 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(struct GHash *library_weak_reference_mapping, const char *library_filepath, const char *library_id_name, @@ -242,9 +303,9 @@ void BKE_main_library_weak_reference_remove_item(struct GHash *library_weak_refe #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 \ } \ @@ -284,16 +345,57 @@ void BKE_main_library_weak_reference_remove_item(struct GHash *library_weak_refe } \ ((void)0) +/** + * Generates a raw .blend file thumbnail data from given image. + * + * \param bmain: If not NULL, also store generated data in this Main. + * \param img: ImBuf image to generate thumbnail data from. + * \return The generated .blend file raw thumbnail data. + */ struct BlendThumbnail *BKE_main_thumbnail_from_imbuf(struct Main *bmain, struct ImBuf *img); +/** + * Generates an image from raw .blend file thumbnail \a data. + * + * \param bmain: Use this bmain->blen_thumb data if given \a data is NULL. + * \param data: Raw .blend file thumbnail data. + * \return An ImBuf from given data, or NULL if invalid. + */ struct ImBuf *BKE_main_thumbnail_to_imbuf(struct Main *bmain, struct BlendThumbnail *data); +/** + * Generates an empty (black) thumbnail for given Main. + */ void BKE_main_thumbnail_create(struct Main *bmain); +/** + * Return file-path of given \a main. + */ const char *BKE_main_blendfile_path(const struct Main *bmain) ATTR_NONNULL(); +/** + * Return file-path of global main #G_MAIN. + * + * \warning Usage is not recommended, + * you should always try to get a valid Main pointer from context. + */ const char *BKE_main_blendfile_path_from_global(void); +/** + * \return A pointer to the \a ListBase of given \a bmain for requested \a type ID type. + */ struct ListBase *which_libbase(struct Main *bmain, short type); //#define INDEX_ID_MAX 41 +/** + * Put the pointers to all the #ListBase structs in given `bmain` into the `*lb[INDEX_ID_MAX]` + * array, and return the number of those for convenience. + * + * This is useful for generic traversal of all the blocks in a #Main (by traversing all the lists + * in turn), without worrying about block types. + * + * \param lb: Array of lists #INDEX_ID_MAX in length. + * + * \note The order of each ID type #ListBase in the array is determined by the `INDEX_ID_<IDTYPE>` + * enum definitions in `DNA_ID.h`. See also the #FOREACH_MAIN_ID_BEGIN macro in `BKE_main.h` + */ int set_listbasepointers(struct Main *main, struct ListBase *lb[]); #define MAIN_VERSION_ATLEAST(main, ver, subver) \ @@ -304,6 +406,13 @@ int set_listbasepointers(struct Main *main, struct ListBase *lb[]); ((main)->versionfile < (ver) || \ ((main)->versionfile == (ver) && (main)->subversionfile < (subver))) +/** + * The size of thumbnails (optionally) stored in the `.blend` files header. + * + * NOTE(@campbellbarton): This is kept small as it's stored uncompressed in the `.blend` file, + * where a larger size would increase the size of every `.blend` file unreasonably. + * If we wanted to increase the size, we'd want to use compression (JPEG or similar). + */ #define BLEN_THUMB_SIZE 128 #define BLEN_THUMB_MEMSIZE(_x, _y) \ diff --git a/source/blender/blenkernel/BKE_main_idmap.h b/source/blender/blenkernel/BKE_main_idmap.h index ff69883f0fb..16b0c710357 100644 --- a/source/blender/blenkernel/BKE_main_idmap.h +++ b/source/blender/blenkernel/BKE_main_idmap.h @@ -44,10 +44,21 @@ enum { MAIN_IDMAP_TYPE_UUID = 1 << 1, }; +/** + * Generate mapping from ID type/name to ID pointer for given \a bmain. + * + * \note When used during undo/redo, there is no guaranty that ID pointers from UI area are not + * pointing to freed memory (when some IDs have been deleted). To avoid crashes in those cases, one + * can provide the 'old' (aka current) Main database as reference. #BKE_main_idmap_lookup_id will + * then check that given ID does exist in \a old_bmain before trying to use it. + * + * \param create_valid_ids_set: If \a true, generate a reference to prevent freed memory accesses. + * \param old_bmain: If not NULL, its IDs will be added the valid references set. + */ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain, - const bool create_valid_ids_set, + bool create_valid_ids_set, struct Main *old_bmain, - const int idmap_types) ATTR_WARN_UNUSED_RESULT + int idmap_types) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); void BKE_main_idmap_destroy(struct IDNameLib_Map *id_map) ATTR_NONNULL(); @@ -67,8 +78,7 @@ struct ID *BKE_main_idmap_lookup_id(struct IDNameLib_Map *id_map, ATTR_NONNULL(1, 2); struct ID *BKE_main_idmap_lookup_uuid(struct IDNameLib_Map *id_map, - const uint session_uuid) ATTR_WARN_UNUSED_RESULT - ATTR_NONNULL(1); + uint session_uuid) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_mask.h b/source/blender/blenkernel/BKE_mask.h index 8e2f6e6f10c..e17f7eb4e85 100644 --- a/source/blender/blenkernel/BKE_mask.h +++ b/source/blender/blenkernel/BKE_mask.h @@ -56,16 +56,20 @@ typedef enum { MASK_HANDLE_MODE_INDIVIDUAL_HANDLES = 2, } eMaskhandleMode; -struct MaskSplinePoint *BKE_mask_spline_point_array(struct MaskSpline *spline); -struct MaskSplinePoint *BKE_mask_spline_point_array_from_point( - struct MaskSpline *spline, const struct MaskSplinePoint *point_ref); +/* -------------------------------------------------------------------- */ +/** \name Mask Layers + * \{ */ -/* mask layers */ struct MaskLayer *BKE_mask_layer_new(struct Mask *mask, const char *name); +/** + * \note The returned mask-layer may be hidden, caller needs to check. + */ struct MaskLayer *BKE_mask_layer_active(struct Mask *mask); void BKE_mask_layer_active_set(struct Mask *mask, struct MaskLayer *masklay); void BKE_mask_layer_remove(struct Mask *mask, struct MaskLayer *masklay); +/** \brief Free all animation keys for a mask layer. + */ void BKE_mask_layer_free_shapes(struct MaskLayer *masklay); void BKE_mask_layer_free(struct MaskLayer *masklay); void BKE_mask_layer_free_list(struct ListBase *masklayers); @@ -83,7 +87,16 @@ void BKE_mask_layer_rename(struct Mask *mask, struct MaskLayer *BKE_mask_layer_copy(const struct MaskLayer *masklay); void BKE_mask_layer_copy_list(struct ListBase *masklayers_new, const struct ListBase *masklayers); -/* splines */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Splines + * \{ */ + +struct MaskSplinePoint *BKE_mask_spline_point_array(struct MaskSpline *spline); +struct MaskSplinePoint *BKE_mask_spline_point_array_from_point( + struct MaskSpline *spline, const struct MaskSplinePoint *point_ref); + struct MaskSpline *BKE_mask_spline_add(struct MaskLayer *masklay); bool BKE_mask_spline_remove(struct MaskLayer *mask_layer, struct MaskSpline *spline); void BKE_mask_point_direction_switch(struct MaskSplinePoint *point); @@ -102,9 +115,14 @@ float BKE_mask_spline_project_co(struct MaskSpline *spline, struct MaskSplinePoint *point, float start_u, const float co[2], - const eMaskSign sign); + eMaskSign sign); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Point + * \{ */ -/* point */ eMaskhandleMode BKE_mask_point_handles_mode_get(const struct MaskSplinePoint *point); void BKE_mask_point_handle(const struct MaskSplinePoint *point, eMaskWhichHandle which_handle, @@ -126,20 +144,23 @@ void BKE_mask_point_normal(struct MaskSpline *spline, float n[2]); float BKE_mask_point_weight_scalar(struct MaskSpline *spline, struct MaskSplinePoint *point, - const float u); -float BKE_mask_point_weight(struct MaskSpline *spline, - struct MaskSplinePoint *point, - const float u); + float u); +float BKE_mask_point_weight(struct MaskSpline *spline, struct MaskSplinePoint *point, float u); struct MaskSplinePointUW *BKE_mask_point_sort_uw(struct MaskSplinePoint *point, struct MaskSplinePointUW *uw); void BKE_mask_point_add_uw(struct MaskSplinePoint *point, float u, float w); -void BKE_mask_point_select_set(struct MaskSplinePoint *point, const bool do_select); +void BKE_mask_point_select_set(struct MaskSplinePoint *point, bool do_select); void BKE_mask_point_select_set_handle(struct MaskSplinePoint *point, - const eMaskWhichHandle which_handle, - const bool do_select); + eMaskWhichHandle which_handle, + bool do_select); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name General + * \{ */ -/* general */ struct Mask *BKE_mask_new(struct Main *bmain, const char *name); void BKE_mask_coord_from_frame(float r_co[2], const float co[2], const float frame_size[2]); @@ -151,6 +172,9 @@ void BKE_mask_coord_from_image(struct Image *image, struct ImageUser *iuser, float r_co[2], const float co[2]); +/** + * Inverse of #BKE_mask_coord_from_image. + */ void BKE_mask_coord_to_frame(float r_co[2], const float co[2], const float frame_size[2]); void BKE_mask_coord_to_movieclip(struct MovieClip *clip, struct MovieClipUser *user, @@ -161,21 +185,34 @@ void BKE_mask_coord_to_image(struct Image *image, float r_co[2], const float co[2]); -/* parenting */ +/** \} */ -void BKE_mask_evaluate(struct Mask *mask, const float ctime, const bool do_newframe); -void BKE_mask_layer_evaluate(struct MaskLayer *masklay, const float ctime, const bool do_newframe); +/* -------------------------------------------------------------------- */ +/** \name Parenting + * \{ */ + +void BKE_mask_evaluate(struct Mask *mask, float ctime, bool do_newframe); +void BKE_mask_layer_evaluate(struct MaskLayer *masklay, float ctime, bool do_newframe); void BKE_mask_parent_init(struct MaskParent *parent); void BKE_mask_calc_handle_adjacent_interp(struct MaskSpline *spline, struct MaskSplinePoint *point, - const float u); + float u); +/** + * Calculates the tangent of a point by its previous and next + * (ignoring handles - as if its a poly line). + */ void BKE_mask_calc_tangent_polyline(struct MaskSpline *spline, struct MaskSplinePoint *point, float t[2]); void BKE_mask_calc_handle_point(struct MaskSpline *spline, struct MaskSplinePoint *point); +/** + * \brief Resets auto handles even for non-auto bezier points + * + * Useful for giving sane defaults. + */ void BKE_mask_calc_handle_point_auto(struct MaskSpline *spline, struct MaskSplinePoint *point, - const bool do_recalc_length); + bool do_recalc_length); void BKE_mask_get_handle_point_adjacent(struct MaskSpline *spline, struct MaskSplinePoint *point, struct MaskSplinePoint **r_point_prev, @@ -186,24 +223,43 @@ void BKE_mask_point_parent_matrix_get(struct MaskSplinePoint *point, float ctime, float parent_matrix[3][3]); -/* animation */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Animation + * \{ */ + int BKE_mask_layer_shape_totvert(struct MaskLayer *masklay); +/** + * Inverse of #BKE_mask_layer_shape_to_mask + */ void BKE_mask_layer_shape_from_mask(struct MaskLayer *masklay, struct MaskLayerShape *masklay_shape); +/** + * Inverse of #BKE_mask_layer_shape_from_mask + */ void BKE_mask_layer_shape_to_mask(struct MaskLayer *masklay, struct MaskLayerShape *masklay_shape); +/** + * \note Linear interpolation only. + */ void BKE_mask_layer_shape_to_mask_interp(struct MaskLayer *masklay, struct MaskLayerShape *masklay_shape_a, struct MaskLayerShape *masklay_shape_b, - const float fac); -struct MaskLayerShape *BKE_mask_layer_shape_find_frame(struct MaskLayer *masklay, const int frame); + float fac); +struct MaskLayerShape *BKE_mask_layer_shape_find_frame(struct MaskLayer *masklay, int frame); +/** + * When returning 2 - the frame isn't found but before/after frames are. + */ int BKE_mask_layer_shape_find_frame_range(struct MaskLayer *masklay, - const float frame, + float frame, struct MaskLayerShape **r_masklay_shape_a, struct MaskLayerShape **r_masklay_shape_b); -struct MaskLayerShape *BKE_mask_layer_shape_alloc(struct MaskLayer *masklay, const int frame); +/** + * \note Does *not* add to the list. + */ +struct MaskLayerShape *BKE_mask_layer_shape_alloc(struct MaskLayer *masklay, int frame); void BKE_mask_layer_shape_free(struct MaskLayerShape *masklay_shape); -struct MaskLayerShape *BKE_mask_layer_shape_verify_frame(struct MaskLayer *masklay, - const int frame); +struct MaskLayerShape *BKE_mask_layer_shape_verify_frame(struct MaskLayer *masklay, int frame); struct MaskLayerShape *BKE_mask_layer_shape_duplicate(struct MaskLayerShape *masklay_shape); void BKE_mask_layer_shape_unlink(struct MaskLayer *masklay, struct MaskLayerShape *masklay_shape); void BKE_mask_layer_shape_sort(struct MaskLayer *masklay); @@ -214,19 +270,42 @@ bool BKE_mask_layer_shape_spline_from_index(struct MaskLayer *masklay, int *r_index); int BKE_mask_layer_shape_spline_to_index(struct MaskLayer *masklay, struct MaskSpline *spline); +/** + * When a new points added, resizing all shape-key arrays. + */ void BKE_mask_layer_shape_changed_add(struct MaskLayer *masklay, int index, bool do_init, bool do_init_interpolate); +/** + * Move array elements to account for removed point. + */ void BKE_mask_layer_shape_changed_remove(struct MaskLayer *masklay, int index, int count); int BKE_mask_get_duration(struct Mask *mask); -/* clipboard */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Clipboard + * \{ */ + +/** + * Free the clipboard. + */ void BKE_mask_clipboard_free(void); +/** + * Copy selected visible splines from the given layer to clipboard. + */ void BKE_mask_clipboard_copy_from_layer(struct MaskLayer *mask_layer); +/** + * Check clipboard is empty. + */ bool BKE_mask_clipboard_is_empty(void); +/** + * Paste the contents of clipboard to given mask layer. + */ void BKE_mask_clipboard_paste_to_layer(struct Main *bmain, struct MaskLayer *mask_layer); #define MASKPOINT_ISSEL_ANY(p) ((((p)->bezt.f1 | (p)->bezt.f2 | (p)->bezt.f3) & SELECT) != 0) @@ -260,29 +339,40 @@ void BKE_mask_clipboard_paste_to_layer(struct Main *bmain, struct MaskLayer *mas } \ (void)0 +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Evaluation + * \{ */ + #define MASK_RESOL_MAX 128 /* mask_evaluate.c */ + unsigned int BKE_mask_spline_resolution(struct MaskSpline *spline, int width, int height); unsigned int BKE_mask_spline_feather_resolution(struct MaskSpline *spline, int width, int height); -int BKE_mask_spline_differentiate_calc_total(const struct MaskSpline *spline, - const unsigned int resol); +int BKE_mask_spline_differentiate_calc_total(const struct MaskSpline *spline, unsigned int resol); float (*BKE_mask_spline_differentiate_with_resolution(struct MaskSpline *spline, - const unsigned int resol, + unsigned int resol, unsigned int *r_tot_diff_point))[2]; void BKE_mask_spline_feather_collapse_inner_loops(struct MaskSpline *spline, float (*feather_points)[2], - const unsigned int tot_feather_point); + unsigned int tot_feather_point); float (*BKE_mask_spline_differentiate( struct MaskSpline *spline, int width, int height, unsigned int *r_tot_diff_point))[2]; +/** + * values align with #BKE_mask_spline_differentiate_with_resolution + * when \a resol arguments match. + */ float (*BKE_mask_spline_feather_differentiated_points_with_resolution( struct MaskSpline *spline, - const unsigned int resol, - const bool do_feather_isect, + unsigned int resol, + bool do_feather_isect, unsigned int *r_tot_feather_point))[2]; /* *** mask point functions which involve evaluation *** */ + float (*BKE_mask_spline_feather_points(struct MaskSpline *spline, int *tot_feather_point))[2]; float *BKE_mask_point_segment_diff(struct MaskSpline *spline, @@ -291,19 +381,28 @@ float *BKE_mask_point_segment_diff(struct MaskSpline *spline, int height, unsigned int *r_tot_diff_point); +/* *** mask point functions which involve evaluation *** */ + float *BKE_mask_point_segment_feather_diff(struct MaskSpline *spline, struct MaskSplinePoint *point, int width, int height, unsigned int *tot_feather_point); -void BKE_mask_layer_evaluate_animation(struct MaskLayer *masklay, const float ctime); -void BKE_mask_layer_evaluate_deform(struct MaskLayer *masklay, const float ctime); +void BKE_mask_layer_evaluate_animation(struct MaskLayer *masklay, float ctime); +void BKE_mask_layer_evaluate_deform(struct MaskLayer *masklay, float ctime); void BKE_mask_eval_animation(struct Depsgraph *depsgraph, struct Mask *mask); void BKE_mask_eval_update(struct Depsgraph *depsgraph, struct Mask *mask); +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Rasterization + * \{ */ + /* mask_rasterize.c */ + struct MaskRasterHandle; typedef struct MaskRasterHandle MaskRasterHandle; @@ -311,18 +410,23 @@ MaskRasterHandle *BKE_maskrasterize_handle_new(void); void BKE_maskrasterize_handle_free(MaskRasterHandle *mr_handle); void BKE_maskrasterize_handle_init(MaskRasterHandle *mr_handle, struct Mask *mask, - const int width, - const int height, - const bool do_aspect_correct, - const bool do_mask_aa, - const bool do_feather); + int width, + int height, + bool do_aspect_correct, + bool do_mask_aa, + bool do_feather); float BKE_maskrasterize_handle_sample(MaskRasterHandle *mr_handle, const float xy[2]); +/** + * \brief Rasterize a buffer from a single mask (threaded execution). + */ void BKE_maskrasterize_buffer(MaskRasterHandle *mr_handle, - const unsigned int width, - const unsigned int height, + unsigned int width, + unsigned int height, float *buffer); +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_material.h b/source/blender/blenkernel/BKE_material.h index b1eaf7207fa..11746f445e4 100644 --- a/source/blender/blenkernel/BKE_material.h +++ b/source/blender/blenkernel/BKE_material.h @@ -34,23 +34,38 @@ struct Object; struct Scene; struct bNode; -/* Module */ +/* -------------------------------------------------------------------- */ +/** \name Module + * \{ */ void BKE_materials_init(void); void BKE_materials_exit(void); -/* Materials */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Materials + * \{ */ void BKE_object_materials_test(struct Main *bmain, struct Object *ob, struct ID *id); void BKE_objects_materials_test_all(struct Main *bmain, struct ID *id); void BKE_object_material_resize(struct Main *bmain, struct Object *ob, - const short totcol, + short totcol, bool do_id_user); void BKE_object_material_remap(struct Object *ob, const unsigned int *remap); +/** + * Calculate a material remapping from \a ob_src to \a ob_dst. + * + * \param remap_src_to_dst: An array the size of `ob_src->totcol` + * where index values are filled in which map to \a ob_dst materials. + */ void BKE_object_material_remap_calc(struct Object *ob_dst, struct Object *ob_src, short *remap_src_to_dst); +/** + * Copy materials from evaluated geometry to the original geometry of an object. + */ void BKE_object_material_from_eval_data(struct Main *bmain, struct Object *ob_orig, struct ID *data_eval); @@ -61,10 +76,17 @@ void BKE_gpencil_material_attr_init(struct Material *ma); /* UNUSED */ // void automatname(struct Material *); -/* material slots */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Material Slots + * \{ */ struct Material ***BKE_object_material_array_p(struct Object *ob); short *BKE_object_material_len_p(struct Object *ob); +/** + * \note Same as #BKE_object_material_len_p but for ID's. + */ struct Material ***BKE_id_material_array_p(struct ID *id); /* same but for ID's */ short *BKE_id_material_len_p(struct ID *id); @@ -81,11 +103,14 @@ struct Material *BKE_object_material_get(struct Object *ob, short act); void BKE_id_material_assign(struct Main *bmain, struct ID *id, struct Material *ma, short act); void BKE_object_material_assign( struct Main *bmain, struct Object *ob, struct Material *ma, short act, int assign_type); +/** + * \warning this calls many more update calls per object then are needed, could be optimized. + */ void BKE_object_material_array_assign(struct Main *bmain, struct Object *ob, struct Material ***matar, int totcol, - const bool to_object_only); + bool to_object_only); 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); @@ -99,7 +124,12 @@ void BKE_texpaint_slot_refresh_cache(struct Scene *scene, struct Material *ma); void BKE_texpaint_slots_refresh_object(struct Scene *scene, struct Object *ob); struct bNode *BKE_texpaint_slot_material_find_node(struct Material *ma, short texpaint_slot); -/* rna api */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name RNA API + * \{ */ + void BKE_id_materials_copy(struct Main *bmain, struct ID *id_src, struct ID *id_dst); void BKE_id_material_resize(struct Main *bmain, struct ID *id, short totcol, bool do_id_user); void BKE_id_material_append(struct Main *bmain, struct ID *id, struct Material *ma); @@ -109,23 +139,57 @@ struct Material *BKE_id_material_pop(struct Main *bmain, int index); void BKE_id_material_clear(struct Main *bmain, struct ID *id); -/* eval api */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Evaluation API + * \{ */ + +/** + * On evaluated objects the number of materials on an object and its data might go out of sync. + * This is because during evaluation materials can be added/removed on the object data. + * + * For rendering or exporting we generally use the materials on the object data. However, some + * material indices might be overwritten by the object. + */ struct Material *BKE_object_material_get_eval(struct Object *ob, short act); int BKE_object_material_count_eval(struct Object *ob); void BKE_id_material_eval_assign(struct ID *id, int slot, struct Material *material); +/** + * Add an empty material slot if the id has no material slots. This material slot allows the + * material to be overwritten by object-linked materials. + */ void BKE_id_material_eval_ensure_default_slot(struct ID *id); -/* rendering */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Rendering + * \{ */ + +/** + * \param r_col: current value. + * \param col: new value. + * \param fac: Zero for is no change. + */ +void ramp_blend(int type, float r_col[3], float fac, const float col[3]); + +/** \} */ -void ramp_blend(int type, float r_col[3], const float fac, const float col[3]); +/* -------------------------------------------------------------------- */ +/** \name Copy/Paste + * \{ */ -/* copy/paste */ void BKE_material_copybuf_clear(void); void BKE_material_copybuf_free(void); void BKE_material_copybuf_copy(struct Main *bmain, struct Material *ma); void BKE_material_copybuf_paste(struct Main *bmain, struct Material *ma); -/* Default Materials */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Default Materials + * \{ */ struct Material *BKE_material_default_empty(void); struct Material *BKE_material_default_holdout(void); @@ -135,12 +199,18 @@ struct Material *BKE_material_default_gpencil(void); void BKE_material_defaults_free_gpu(void); -/* Dependency graph evaluation. */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dependency graph evaluation + * \{ */ struct Depsgraph; void BKE_material_eval(struct Depsgraph *depsgraph, struct Material *material); +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_mball.h b/source/blender/blenkernel/BKE_mball.h index db4dca14535..fb72b361a0a 100644 --- a/source/blender/blenkernel/BKE_mball.h +++ b/source/blender/blenkernel/BKE_mball.h @@ -41,27 +41,66 @@ bool BKE_mball_is_any_selected(const struct MetaBall *mb); bool BKE_mball_is_any_selected_multi(struct Base **bases, int bases_len); bool BKE_mball_is_any_unselected(const struct MetaBall *mb); bool BKE_mball_is_basis_for(struct Object *ob1, struct Object *ob2); +/** + * Test, if \a ob is a basis meta-ball. + * + * It test last character of Object ID name. + * If last character is digit it return 0, else it return 1. + */ bool BKE_mball_is_basis(struct Object *ob); +/** + * This function finds the basis meta-ball. + * + * Basis meta-ball doesn't include any number at the end of + * its name. All meta-balls with same base of name can be + * blended. meta-balls with different basic name can't be blended. + * + * \warning #BKE_mball_is_basis() can fail on returned object, see function docs for details. + */ struct Object *BKE_mball_basis_find(struct Scene *scene, struct Object *ob); +/** + * Compute bounding box of all meta-elements / meta-ball. + * + * Bounding box is computed from polygonized surface. \a ob is + * basic meta-balls (with name `Meta` for example). All other meta-ball objects + * (with names `Meta.001`, `Meta.002`, etc) are included in this bounding-box. + */ void BKE_mball_texspace_calc(struct Object *ob); +/** + * Return or compute bounding-box for given meta-ball object. + */ struct BoundBox *BKE_mball_boundbox_get(struct Object *ob); float *BKE_mball_make_orco(struct Object *ob, struct ListBase *dispbase); +/** + * Copy some properties from object to other meta-ball object with same base name. + * + * When some properties (wire-size, threshold, update flags) of meta-ball are changed, then this + * properties are copied to all meta-balls in same "group" (meta-balls with same base name: + * `MBall`, `MBall.001`, `MBall.002`, etc). The most important is to copy properties to the base + * meta-ball, because this meta-ball influence polygonization of meta-balls. */ void BKE_mball_properties_copy(struct Scene *scene, struct Object *active_object); -bool BKE_mball_minmax_ex(const struct MetaBall *mb, - float min[3], - float max[3], - const float obmat[4][4], - const short flag); +bool BKE_mball_minmax_ex( + const struct MetaBall *mb, float min[3], float max[3], const float obmat[4][4], short flag); + +/* Basic vertex data functions. */ + bool BKE_mball_minmax(const struct MetaBall *mb, float min[3], float max[3]); bool BKE_mball_center_median(const struct MetaBall *mb, float r_cent[3]); bool BKE_mball_center_bounds(const struct MetaBall *mb, float r_cent[3]); -void BKE_mball_transform(struct MetaBall *mb, const float mat[4][4], const bool do_props); +void BKE_mball_transform(struct MetaBall *mb, const float mat[4][4], bool do_props); void BKE_mball_translate(struct MetaBall *mb, const float offset[3]); -struct MetaElem *BKE_mball_element_add(struct MetaBall *mb, const int type); +/** + * Most simple meta-element adding function. + * + * \note don't do context manipulation here (rna uses). + */ +struct MetaElem *BKE_mball_element_add(struct MetaBall *mb, int type); + +/* *** select funcs *** */ int BKE_mball_select_count(const struct MetaBall *mb); int BKE_mball_select_count_multi(struct Base **bases, int bases_len); diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h index b0a8fee1178..e1c706a82dc 100644 --- a/source/blender/blenkernel/BKE_mesh.h +++ b/source/blender/blenkernel/BKE_mesh.h @@ -23,6 +23,7 @@ */ #include "BKE_mesh_types.h" +#include "BLI_compiler_attrs.h" #include "BLI_utildefines.h" struct BLI_Stack; @@ -74,7 +75,7 @@ struct BMesh *BKE_mesh_to_bmesh_ex(const struct Mesh *me, const struct BMeshFromMeshParams *convert_params); struct BMesh *BKE_mesh_to_bmesh(struct Mesh *me, struct Object *ob, - const bool add_key_index, + bool add_key_index, const struct BMeshCreateParams *params); struct Mesh *BKE_mesh_from_bmesh_nomain(struct BMesh *bm, @@ -84,23 +85,56 @@ struct Mesh *BKE_mesh_from_bmesh_for_eval_nomain(struct BMesh *bm, const struct CustomData_MeshMasks *cd_mask_extra, const struct Mesh *me_settings); +/** + * Find the index of the loop in 'poly' which references vertex, + * returns -1 if not found + */ int poly_find_loop_from_vert(const struct MPoly *poly, const struct MLoop *loopstart, uint vert); +/** + * Fill \a r_adj with the loop indices in \a poly adjacent to the + * vertex. Returns the index of the loop matching vertex, or -1 if the + * vertex is not in \a poly + */ int poly_get_adj_loops_from_vert(const struct MPoly *poly, const struct MLoop *mloop, unsigned int vert, unsigned int r_adj[2]); +/** + * Return the index of the edge vert that is not equal to \a v. If + * neither edge vertex is equal to \a v, returns -1. + */ int BKE_mesh_edge_other_vert(const struct MEdge *e, int v); +/** + * Sets each output array element to the edge index if it is a real edge, or -1. + */ void BKE_mesh_looptri_get_real_edges(const struct Mesh *mesh, const struct MLoopTri *looptri, int r_edges[3]); +/** + * 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(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_free_editmesh(struct Mesh *mesh); + +/** + * A version of #BKE_mesh_copy_parameters that is intended for evaluated output + * (the modifier stack for example). + * + * \warning User counts are not handled for ID's. + */ void BKE_mesh_copy_parameters_for_eval(struct Mesh *me_dst, const struct Mesh *me_src); +/** + * Copy user editable settings that we want to preserve + * when a new mesh is based on an existing mesh. + */ void BKE_mesh_copy_parameters(struct Mesh *me_dst, const struct Mesh *me_src); -void BKE_mesh_update_customdata_pointers(struct Mesh *me, const bool do_ensure_tess_cd); +void BKE_mesh_update_customdata_pointers(struct Mesh *me, bool do_ensure_tess_cd); void BKE_mesh_ensure_skin_customdata(struct Mesh *me); struct Mesh *BKE_mesh_new_nomain( @@ -121,12 +155,16 @@ struct Mesh *BKE_mesh_new_nomain_from_template_ex(const struct Mesh *me_src, void BKE_mesh_eval_delete(struct Mesh *mesh_eval); -/* Performs copy for use during evaluation, - * optional referencing original arrays to reduce memory. */ +/** + * Performs copy for use during evaluation, + * optional referencing original arrays to reduce memory. + */ struct Mesh *BKE_mesh_copy_for_eval(const struct Mesh *source, bool reference); -/* These functions construct a new Mesh, - * contrary to BKE_mesh_to_curve_nurblist which modifies ob itself. */ +/** + * These functions construct a new Mesh, + * 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); @@ -136,6 +174,16 @@ bool BKE_mesh_clear_facemap_customdata(struct Mesh *me); float (*BKE_mesh_orco_verts_get(struct Object *ob))[3]; void BKE_mesh_orco_verts_transform(struct Mesh *me, float (*orco)[3], int totvert, int invert); + +/** + * Add a #CD_ORCO layer to the Mesh if there is none already. + */ +void BKE_mesh_orco_ensure(struct Object *ob, struct Mesh *mesh); + +/** + * Rotates the vertices of a face in case v[2] or v[3] (vertex index) is = 0. + * this is necessary to make the if #MFace.v4 check for quads work. + */ int BKE_mesh_mface_index_validate(struct MFace *mface, struct CustomData *mfdata, int mfindex, @@ -145,7 +193,7 @@ void BKE_mesh_assign_object(struct Main *bmain, struct Object *ob, struct Mesh * void BKE_mesh_from_metaball(struct ListBase *lb, struct Mesh *me); void BKE_mesh_to_curve_nurblist(const struct Mesh *me, struct ListBase *nurblist, - const int edge_users_test); + int edge_users_test); void BKE_mesh_to_curve(struct Main *bmain, struct Depsgraph *depsgraph, struct Scene *scene, @@ -164,11 +212,19 @@ void BKE_mesh_material_index_remove(struct Mesh *me, short index); bool BKE_mesh_material_index_used(struct Mesh *me, short index); void BKE_mesh_material_index_clear(struct Mesh *me); void BKE_mesh_material_remap(struct Mesh *me, const unsigned int *remap, unsigned int remap_len); -void BKE_mesh_smooth_flag_set(struct Mesh *me, const bool use_smooth); +void BKE_mesh_smooth_flag_set(struct Mesh *me, bool use_smooth); -/* Needed after converting a mesh with subsurf optimal display to mesh. */ +/** + * Needed after converting a mesh with subsurf optimal display to mesh. + */ void BKE_mesh_edges_set_draw_render(struct Mesh *me); +/** + * Used for unit testing; compares two meshes, checking only + * differences we care about. should be usable with leaf's + * testing framework I get RNA work done, will use hackish + * testing code for now. + */ const char *BKE_mesh_cmp(struct Mesh *me1, struct Mesh *me2, float thresh); struct BoundBox *BKE_mesh_boundbox_get(struct Object *ob); @@ -177,41 +233,59 @@ void BKE_mesh_texspace_calc(struct Mesh *me); void BKE_mesh_texspace_ensure(struct Mesh *me); void BKE_mesh_texspace_get(struct Mesh *me, float r_loc[3], float r_size[3]); void BKE_mesh_texspace_get_reference(struct Mesh *me, - short **r_texflag, + char **r_texflag, float **r_loc, float **r_size); void BKE_mesh_texspace_copy_from_object(struct Mesh *me, struct Object *ob); +/** + * Split faces based on the edge angle and loop normals. + * Matches behavior of face splitting in render engines. + * + * \note Will leave #CD_NORMAL loop data layer which is used by render engines to set shading up. + */ void BKE_mesh_split_faces(struct Mesh *mesh, bool free_loop_normals); -/* Create new mesh from the given object at its current state. +/** + * Create new mesh from the given object at its current state. * The owner of this mesh is unknown, it is up to the caller to decide. * * If preserve_all_data_layers is truth then the modifier stack is re-evaluated to ensure it * preserves all possible custom data layers. * - * NOTE: Dependency graph argument is required when preserve_all_data_layers is truth, and is - * ignored otherwise. */ + * \note Dependency graph argument is required when preserve_all_data_layers is truth, and is + * ignored otherwise. + */ struct Mesh *BKE_mesh_new_from_object(struct Depsgraph *depsgraph, struct Object *object, - const bool preserve_all_data_layers, - const bool preserve_origindex); + bool preserve_all_data_layers, + bool preserve_origindex); -/* This is a version of BKE_mesh_new_from_object() which stores mesh in the given main database. +/** + * This is a version of BKE_mesh_new_from_object() which stores mesh in the given main database. * However, that function enforces object type to be a geometry one, and ensures a mesh is always - * generated, be it empty. */ + * generated, be it empty. + */ struct Mesh *BKE_mesh_new_from_object_to_bmain(struct Main *bmain, struct Depsgraph *depsgraph, struct Object *object, bool preserve_all_data_layers); +/** + * \param use_virtual_modifiers: When enabled calculate virtual-modifiers before applying `md_eval` + * support this since virtual-modifiers are not modifiers from a user perspective, + * allowing shape keys to be included with the modifier being applied, see: T91923. + */ struct Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob_eval, struct ModifierData *md_eval, - const bool build_shapekey_layers); + bool use_virtual_modifiers, + bool build_shapekey_layers); -/* Copies a nomain-Mesh into an existing Mesh. */ +/** + * Copies a nomain-Mesh into an existing Mesh. + */ void BKE_mesh_nomain_to_mesh(struct Mesh *mesh_src, struct Mesh *mesh_dst, struct Object *ob, @@ -221,9 +295,10 @@ void BKE_mesh_nomain_to_meshkey(struct Mesh *mesh_src, struct Mesh *mesh_dst, st /* vertex level transformations & checks (no derived mesh) */ +/* basic vertex data functions */ bool BKE_mesh_minmax(const struct Mesh *me, float r_min[3], float r_max[3]); void BKE_mesh_transform(struct Mesh *me, const float mat[4][4], bool do_keys); -void BKE_mesh_translate(struct Mesh *me, const float offset[3], const bool do_keys); +void BKE_mesh_translate(struct Mesh *me, const float offset[3], bool do_keys); void BKE_mesh_tessface_ensure(struct Mesh *mesh); void BKE_mesh_tessface_clear(struct Mesh *mesh); @@ -232,7 +307,13 @@ void BKE_mesh_do_versions_cd_flag_init(struct Mesh *mesh); void BKE_mesh_mselect_clear(struct Mesh *me); void BKE_mesh_mselect_validate(struct Mesh *me); +/** + * \return the index within `me->mselect`, or -1 + */ int BKE_mesh_mselect_find(struct Mesh *me, int index, int type); +/** + * \return The index of the active element. + */ int BKE_mesh_mselect_active_get(struct Mesh *me, int type); void BKE_mesh_mselect_active_set(struct Mesh *me, int index, int type); @@ -245,10 +326,20 @@ void BKE_mesh_vert_coords_apply_with_mat4(struct Mesh *mesh, const float (*vert_coords)[3], const float mat[4][4]); void BKE_mesh_vert_coords_apply(struct Mesh *mesh, const float (*vert_coords)[3]); -void BKE_mesh_vert_normals_apply(struct Mesh *mesh, const short (*vert_normals)[3]); /* *** mesh_tessellate.c *** */ +/** + * Recreate #MFace Tessellation. + * + * \param do_face_nor_copy: Controls whether the normals from the poly + * are copied to the tessellated faces. + * + * \return number of tessellation faces. + * + * \note This doesn't use multi-threading like #BKE_mesh_recalc_looptri since + * it's not used in many places and #MFace should be phased out. + */ int BKE_mesh_tessface_calc_ex(struct CustomData *fdata, struct CustomData *ldata, struct CustomData *pdata, @@ -256,15 +347,26 @@ int BKE_mesh_tessface_calc_ex(struct CustomData *fdata, int totface, int totloop, int totpoly, - const bool do_face_nor_copy); + bool do_face_nor_copy); void BKE_mesh_tessface_calc(struct Mesh *mesh); +/** + * Calculate tessellation into #MLoopTri which exist only for this purpose. + */ void BKE_mesh_recalc_looptri(const struct MLoop *mloop, const struct MPoly *mpoly, const struct MVert *mvert, int totloop, int totpoly, struct MLoopTri *mlooptri); +/** + * A version of #BKE_mesh_recalc_looptri which takes pre-calculated polygon normals + * (used to avoid having to calculate the face normal for NGON tessellation). + * + * \note Only use this function if normals have already been calculated, there is no need + * to calculate normals just to use this function as it will cause the normals for triangles + * to be calculated which aren't needed for tessellation. + */ void BKE_mesh_recalc_looptri_with_normals(const struct MLoop *mloop, const struct MPoly *mpoly, const struct MVert *mvert, @@ -275,7 +377,83 @@ void BKE_mesh_recalc_looptri_with_normals(const struct MLoop *mloop, /* *** mesh_normals.cc *** */ +/** + * Returns the normals for each vertex, which is defined as the weighted average of the normals + * from a vertices surrounding faces, or the normalized position of vertices connected to no faces. + * \warning May still return null if the mesh is empty. + */ +const float (*BKE_mesh_vertex_normals_ensure(const struct Mesh *mesh))[3]; + +/** + * Return the normal direction of every polygon, which is defined by the winding direction of its + * corners. + * \warning May still return null if the mesh is empty or has no polygons. + */ +const float (*BKE_mesh_poly_normals_ensure(const struct Mesh *mesh))[3]; + +/** + * Tag mesh vertex and face normals to be recalculated when/if they are needed later. + * + * \note Dirty tagged normals are the default state of a new mesh, so tagging them + * dirty explicitly is not always necessary if the mesh is created locally. + */ void BKE_mesh_normals_tag_dirty(struct Mesh *mesh); + +/** + * Check that a mesh with non-dirty normals has vertex and face custom data layers. + * If these asserts fail, it means some area cleared the dirty flag but didn't copy or add the + * normal layers, or removed normals but didn't set the dirty flag. + */ +void BKE_mesh_assert_normals_dirty_or_calculated(const struct Mesh *mesh); + +/** + * Retrieve write access to the vertex normal layer, ensuring that it exists and that it is not + * shared. The provided vertex normals should be the same as if they were calculated automatically. + * + * \note In order to clear the dirty flag, this function should be followed by a call to + * #BKE_mesh_vertex_normals_clear_dirty. This is separate so that normals are still tagged dirty + * while they are being assigned. + */ +float (*BKE_mesh_vertex_normals_for_write(struct Mesh *mesh))[3]; + +/** + * Retrieve write access to the poly normal layer, ensuring that it exists and that it is not + * shared. The provided poly normals should be the same as if they were calculated automatically. + * + * \note In order to clear the dirty flag, this function should be followed by a call to + * #BKE_mesh_poly_normals_clear_dirty. This is separate so that normals are still tagged dirty + * while they are being assigned. + */ +float (*BKE_mesh_poly_normals_for_write(struct Mesh *mesh))[3]; + +/** + * Mark the mesh's vertex normals non-dirty, for when they are calculated or assigned manually. + */ +void BKE_mesh_vertex_normals_clear_dirty(struct Mesh *mesh); + +/** + * Mark the mesh's poly normals non-dirty, for when they are calculated or assigned manually. + */ +void BKE_mesh_poly_normals_clear_dirty(struct Mesh *mesh); + +/** + * Return true if the mesh vertex normals either are not stored or are dirty. + * This can be used to help decide whether to transfer them when copying a mesh. + */ +bool BKE_mesh_vertex_normals_are_dirty(const struct Mesh *mesh); + +/** + * Return true if the mesh polygon normals either are not stored or are dirty. + * This can be used to help decide whether to transfer them when copying a mesh. + */ +bool BKE_mesh_poly_normals_are_dirty(const struct Mesh *mesh); + +/** + * Calculate face normals directly into a result array. + * + * \note Usually #BKE_mesh_poly_normals_ensure is the preferred way to access face normals, + * since they may already be calculated and cached on the mesh. + */ void BKE_mesh_calc_normals_poly(const struct MVert *mvert, int mvert_len, const struct MLoop *mloop, @@ -283,16 +461,19 @@ void BKE_mesh_calc_normals_poly(const struct MVert *mvert, 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]); + +/** + * Calculate vertex and face normals, storing the result in custom data layers on the mesh. + * + * \note It is usually preferable to calculate normals lazily with + * #BKE_mesh_vertex_normals_ensure, but some areas (perhaps unnecessarily) + * can also calculate them eagerly. + */ void BKE_mesh_calc_normals(struct Mesh *me); -void BKE_mesh_ensure_normals(struct Mesh *me); + +/** + * Called after calculating all modifiers. + */ void BKE_mesh_ensure_normals_for_display(struct Mesh *mesh); void BKE_mesh_calc_normals_looptri(struct MVert *mverts, int numVerts, @@ -304,22 +485,28 @@ void BKE_mesh_loop_manifold_fan_around_vert_next(const struct MLoop *mloops, const struct MPoly *mpolys, const int *loop_to_poly, const int *e2lfan_curr, - const uint mv_pivot_index, + uint mv_pivot_index, const struct MLoop **r_mlfan_curr, int *r_mlfan_curr_index, int *r_mlfan_vert_index, int *r_mpfan_curr_index); +/** + * 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 auto-smooth! + */ void BKE_edges_sharp_from_angle_set(const struct MVert *mverts, - const int numVerts, + int numVerts, struct MEdge *medges, - const int numEdges, + int numEdges, struct MLoop *mloops, - const int numLoops, + int numLoops, struct MPoly *mpolys, const float (*polynors)[3], - const int numPolys, - const float split_angle); + int numPolys, + float split_angle); /** * References a contiguous loop-fan with normal offset vars. @@ -372,28 +559,51 @@ enum { }; /* Low-level custom normals functions. */ -void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr, - const int numLoops, - const char data_type); +void BKE_lnor_spacearr_init(MLoopNorSpaceArray *lnors_spacearr, int numLoops, char data_type); void BKE_lnor_spacearr_clear(MLoopNorSpaceArray *lnors_spacearr); void BKE_lnor_spacearr_free(MLoopNorSpaceArray *lnors_spacearr); +/** + * 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); +/** + * 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); MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr); +/** + * Should only be called once. + * Beware, this modifies ref_vec and other_vec in place! + * In case no valid space can be generated, ref_alpha and ref_beta are set to zero + * (which means 'use auto lnors'). + */ void BKE_lnor_space_define(MLoopNorSpace *lnor_space, const float lnor[3], float vec_ref[3], float vec_other[3], struct BLI_Stack *edge_vectors); +/** + * 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 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, - const int ml_index, + int ml_index, void *bm_loop, - const bool is_single); + bool is_single); void BKE_lnor_space_custom_data_to_normal(MLoopNorSpace *lnor_space, const short clnor_data[2], float r_custom_lnor[3]); @@ -402,59 +612,97 @@ void BKE_lnor_space_custom_normal_to_data(MLoopNorSpace *lnor_space, short r_clnor_data[2]); /* Medium-level custom normals functions. */ + +/** + * Compute split normals, i.e. vertex normals associated with each poly (hence 'loop normals'). + * Useful to materialize sharp edges (or non-smooth faces) without actually modifying the geometry + * (splitting edges). + */ void BKE_mesh_normals_loop_split(const struct MVert *mverts, - const int numVerts, + const float (*vert_normals)[3], + int numVerts, struct MEdge *medges, - const int numEdges, + int numEdges, struct MLoop *mloops, float (*r_loopnors)[3], - const int numLoops, + int numLoops, struct MPoly *mpolys, const float (*polynors)[3], - const int numPolys, - const bool use_split_normals, - const float split_angle, + int numPolys, + bool use_split_normals, + float split_angle, MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], int *r_loop_to_poly); void BKE_mesh_normals_loop_custom_set(const struct MVert *mverts, - const int numVerts, + const float (*vert_normals)[3], + int numVerts, struct MEdge *medges, - const int numEdges, + int numEdges, struct MLoop *mloops, float (*r_custom_loopnors)[3], - const int numLoops, + int numLoops, struct MPoly *mpolys, const float (*polynors)[3], - const int numPolys, + int numPolys, short (*r_clnors_data)[2]); void BKE_mesh_normals_loop_custom_from_vertices_set(const struct MVert *mverts, + const float (*vert_normals)[3], float (*r_custom_vertnors)[3], - const int numVerts, + int numVerts, struct MEdge *medges, - const int numEdges, + int numEdges, struct MLoop *mloops, - const int numLoops, + int numLoops, struct MPoly *mpolys, const float (*polynors)[3], - const int numPolys, + int numPolys, short (*r_clnors_data)[2]); -void BKE_mesh_normals_loop_to_vertex(const int numVerts, +/** + * Computes average per-vertex normals from given custom loop normals. + * + * \param clnors: The computed custom loop normals. + * \param r_vert_clnors: The (already allocated) array where to store averaged per-vertex normals. + */ +void BKE_mesh_normals_loop_to_vertex(int numVerts, const struct MLoop *mloops, - const int numLoops, + int numLoops, const float (*clnors)[3], float (*r_vert_clnors)[3]); -/* High-level custom normals functions. */ +/** + * High-level custom normals functions. + */ bool BKE_mesh_has_custom_loop_normals(struct Mesh *me); void BKE_mesh_calc_normals_split(struct Mesh *mesh); +/** + * Compute 'split' (aka loop, or per face corner's) normals. + * + * \param r_lnors_spacearr: Allows to get computed loop normal space array. + * That data, among other things, contains 'smooth fan' info, useful e.g. + * to split geometry along sharp edges. + */ void BKE_mesh_calc_normals_split_ex(struct Mesh *mesh, struct MLoopNorSpaceArray *r_lnors_spacearr); +/** + * Higher level functions hiding most of the code needed around call to + * #BKE_mesh_normals_loop_custom_set(). + * + * \param r_custom_loopnors: is not const, since code will replace zero_v3 normals there + * with automatically computed vectors. + */ void BKE_mesh_set_custom_normals(struct Mesh *mesh, float (*r_custom_loopnors)[3]); +/** + * Higher level functions hiding most of the code needed around call to + * #BKE_mesh_normals_loop_custom_from_vertices_set(). + * + * \param r_custom_vertnors: is not const, since code will replace zero_v3 normals there + * with automatically computed vectors. + */ void BKE_mesh_set_custom_normals_from_vertices(struct Mesh *mesh, float (*r_custom_vertnors)[3]); /* *** mesh_evaluate.cc *** */ @@ -471,6 +719,7 @@ void BKE_mesh_calc_poly_center(const struct MPoly *mpoly, const struct MLoop *loopstart, const struct MVert *mvarray, float r_cent[3]); +/* NOTE: passing poly-normal is only a speedup so we can skip calculating it. */ float BKE_mesh_calc_poly_area(const struct MPoly *mpoly, const struct MLoop *loopstart, const struct MVert *mvarray); @@ -489,21 +738,48 @@ void BKE_mesh_poly_edgebitmap_insert(unsigned int *edge_bitmap, const struct MLoop *mloop); bool BKE_mesh_center_median(const struct Mesh *me, float r_cent[3]); +/** + * 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 struct Mesh *me, float r_cent[3]); bool BKE_mesh_center_bounds(const struct Mesh *me, float r_cent[3]); bool BKE_mesh_center_of_surface(const struct Mesh *me, float r_cent[3]); +/** + * \note Mesh must be manifold with consistent face-winding, + * see #mesh_calc_poly_volume_centroid for details. + */ bool BKE_mesh_center_of_volume(const struct Mesh *me, float r_cent[3]); +/** + * Calculate the volume and center. + * + * \param r_volume: Volume (unsigned). + * \param r_center: Center of mass. + */ void BKE_mesh_calc_volume(const struct MVert *mverts, - const int mverts_num, + int mverts_num, const struct MLoopTri *mlooptri, - const int looptri_num, + int looptri_num, const struct MLoop *mloop, float *r_volume, float r_center[3]); /* tessface */ void BKE_mesh_convert_mfaces_to_mpolys(struct Mesh *mesh); +/** + * 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(struct Mesh *mesh); void BKE_mesh_convert_mfaces_to_mpolys_ex(struct ID *id, struct CustomData *fdata, @@ -520,15 +796,32 @@ void BKE_mesh_convert_mfaces_to_mpolys_ex(struct ID *id, struct MLoop **r_mloop, struct MPoly **r_mpoly); -void BKE_mesh_mdisp_flip(struct MDisps *md, const bool use_loop_mdisp_flip); +/** + * 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(struct MDisps *md, bool use_loop_mdisp_flip); +/** + * 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(struct MPoly *mpoly, struct MLoop *mloop, struct CustomData *ldata, float (*lnors)[3], struct MDisps *mdisp, - const bool use_loop_mdisp_flip); + bool use_loop_mdisp_flip); void BKE_mesh_polygon_flip(struct MPoly *mpoly, struct MLoop *mloop, struct CustomData *ldata); +/** + * Flip (invert winding of) all polygons (used to inverse their normals). + * + * \note Invalidates tessellation, caller must handle that. + */ void BKE_mesh_polygons_flip(struct MPoly *mpoly, struct MLoop *mloop, struct CustomData *ldata, @@ -541,48 +834,98 @@ enum { MESH_MERGE_VERTS_DUMP_IF_MAPPED, MESH_MERGE_VERTS_DUMP_IF_EQUAL, }; +/** + * Merge Verts + * + * This frees the given mesh and returns a new mesh. + * + * \param vtargetmap: The table that maps vertices to target vertices. a value of -1 + * indicates a vertex is a target, and is to be kept. + * This array is aligned with 'mesh->totvert' + * \warning \a vtargetmap must **not** contain any chained mapping (v1 -> v2 -> v3 etc.), + * this is not supported and will likely generate corrupted geometry. + * + * \param tot_vtargetmap: The number of non '-1' values in vtargetmap. (not the size) + * + * \param merge_mode: enum with two modes. + * - #MESH_MERGE_VERTS_DUMP_IF_MAPPED + * When called by the Mirror Modifier, + * In this mode it skips any faces that have all vertices merged (to avoid creating pairs + * of faces sharing the same set of vertices) + * - #MESH_MERGE_VERTS_DUMP_IF_EQUAL + * When called by the Array Modifier, + * In this mode, faces where all vertices are merged are double-checked, + * to see whether all target vertices actually make up a poly already. + * Indeed it could be that all of a poly's vertices are merged, + * but merged to vertices that do not make up a single poly, + * in which case the original poly should not be dumped. + * Actually this later behavior could apply to the Mirror Modifier as well, + * but the additional checks are costly and not necessary in the case of mirror, + * because each vertex is only merged to its own mirror. + * + * \note #BKE_mesh_tessface_calc_ex has to run on the returned DM + * if you want to access tess-faces. + */ struct Mesh *BKE_mesh_merge_verts(struct Mesh *mesh, const int *vtargetmap, - const int tot_vtargetmap, - const int merge_mode); + int tot_vtargetmap, + int merge_mode); -/* flush flags */ +/* Flush flags. */ + +/** + * Update the hide flag for edges and faces from the corresponding flag in verts. + */ void BKE_mesh_flush_hidden_from_verts_ex(const struct MVert *mvert, const struct MLoop *mloop, struct MEdge *medge, - const int totedge, + int totedge, struct MPoly *mpoly, - const int totpoly); + int totpoly); void BKE_mesh_flush_hidden_from_verts(struct Mesh *me); void BKE_mesh_flush_hidden_from_polys_ex(struct MVert *mvert, const struct MLoop *mloop, struct MEdge *medge, - const int totedge, + int totedge, const struct MPoly *mpoly, - const int totpoly); + int totpoly); void BKE_mesh_flush_hidden_from_polys(struct Mesh *me); +/** + * simple poly -> vert/edge selection. + */ void BKE_mesh_flush_select_from_polys_ex(struct MVert *mvert, - const int totvert, + int totvert, const struct MLoop *mloop, struct MEdge *medge, - const int totedge, + int totedge, const struct MPoly *mpoly, - const int totpoly); + int totpoly); void BKE_mesh_flush_select_from_polys(struct Mesh *me); void BKE_mesh_flush_select_from_verts_ex(const struct MVert *mvert, - const int totvert, + int totvert, const struct MLoop *mloop, struct MEdge *medge, - const int totedge, + int totedge, struct MPoly *mpoly, - const int totpoly); + int totpoly); void BKE_mesh_flush_select_from_verts(struct Mesh *me); /* spatial evaluation */ +/** + * 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 struct MPoly *mpoly, - const int totpoly, + int totpoly, const struct MLoop *mloop, - const int totvert, + int totvert, const float (*vert_cos_src)[3], const float (*vert_cos_dst)[3], @@ -592,10 +935,38 @@ void BKE_mesh_calc_relative_deform(const struct MPoly *mpoly, /* *** mesh_validate.c *** */ -bool BKE_mesh_validate(struct Mesh *me, const bool do_verbose, const bool cddata_check_mask); +/** + * Validates and corrects a Mesh. + * + * \returns true if a change is made. + */ +bool BKE_mesh_validate(struct Mesh *me, bool do_verbose, bool cddata_check_mask); +/** + * Checks if a Mesh is valid without any modification. This is always verbose. + * \returns True if the mesh is valid. + */ bool BKE_mesh_is_valid(struct Mesh *me); +/** + * Check all material indices of polygons are valid, invalid ones are set to 0. + * \returns True if the material indices are valid. + */ bool BKE_mesh_validate_material_indices(struct Mesh *me); +/** + * Validate the mesh, \a do_fixes requires \a mesh to be non-null. + * + * \return false if no changes needed to be made. + * + * Vertex Normals + * ============== + * + * While zeroed normals are checked, these checks aren't comprehensive. + * Technically, to detect errors here a normal recalculation and comparison is necessary. + * However this function is mainly to prevent severe errors in geometry + * (invalid data that will crash Blender, or cause some features to behave incorrectly), + * not to detect subtle differences in the resulting normals which could be caused + * by importers that load normals (for example). + */ bool BKE_mesh_validate_arrays(struct Mesh *me, struct MVert *mverts, unsigned int totvert, @@ -608,35 +979,57 @@ bool BKE_mesh_validate_arrays(struct Mesh *me, struct MPoly *mpolys, unsigned int totpoly, struct MDeformVert *dverts, /* assume totvert length */ - const bool do_verbose, - const bool do_fixes, + bool do_verbose, + bool do_fixes, bool *r_change); +/** + * \returns is_valid. + */ bool BKE_mesh_validate_all_customdata(struct CustomData *vdata, - const uint totvert, + uint totvert, struct CustomData *edata, - const uint totedge, + uint totedge, struct CustomData *ldata, - const uint totloop, + uint totloop, struct CustomData *pdata, - const uint totpoly, - const bool check_meshmask, - const bool do_verbose, - const bool do_fixes, + uint totpoly, + bool check_meshmask, + bool do_verbose, + bool do_fixes, bool *r_change); void BKE_mesh_strip_loose_faces(struct Mesh *me); +/** + * Works on both loops and polys! + * + * \note It won't try to guess which loops of an invalid poly to remove! + * this is the work of the caller, to mark those loops. + * See e.g. #BKE_mesh_validate_arrays(). + */ void BKE_mesh_strip_loose_polysloops(struct Mesh *me); void BKE_mesh_strip_loose_edges(struct Mesh *me); -void BKE_mesh_calc_edges_legacy(struct Mesh *me, const bool use_old); +/** + * If the mesh is from a very old blender version, + * convert mface->edcode to edge drawflags + */ +void BKE_mesh_calc_edges_legacy(struct Mesh *me, bool use_old); void BKE_mesh_calc_edges_loose(struct Mesh *mesh); -void BKE_mesh_calc_edges(struct Mesh *mesh, bool keep_existing_edges, const bool select_new_edges); +/** + * Calculate edges from polygons. + */ +void BKE_mesh_calc_edges(struct Mesh *mesh, bool keep_existing_edges, bool select_new_edges); +/** + * Calculate/create edges from tessface data + * + * \param mesh: The mesh to add edges into + */ void BKE_mesh_calc_edges_tessface(struct Mesh *mesh); /* In DerivedMesh.cc */ void BKE_mesh_wrapper_deferred_finalize(struct Mesh *me_eval, - const CustomData_MeshMasks *cd_mask_finalize); + const struct CustomData_MeshMasks *cd_mask_finalize); /* **** Depsgraph evaluation **** */ @@ -649,11 +1042,17 @@ void BKE_mesh_batch_cache_free(struct Mesh *me); extern void (*BKE_mesh_batch_cache_dirty_tag_cb)(struct Mesh *me, eMeshBatchDirtyMode mode); extern void (*BKE_mesh_batch_cache_free_cb)(struct Mesh *me); +/* mesh_debug.c */ +#ifndef NDEBUG +char *BKE_mesh_debug_info(const struct Mesh *me) + ATTR_NONNULL(1) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT; +void BKE_mesh_debug_print(const struct Mesh *me) ATTR_NONNULL(1); +#endif + /* 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_boolean_convert.hh b/source/blender/blenkernel/BKE_mesh_boolean_convert.hh index 59f6e75183e..5a743999803 100644 --- a/source/blender/blenkernel/BKE_mesh_boolean_convert.hh +++ b/source/blender/blenkernel/BKE_mesh_boolean_convert.hh @@ -32,12 +32,22 @@ struct Mesh; namespace blender::meshintersect { +/** + * Do a mesh boolean operation directly on meshes (without going back and forth to BMesh). + * \param meshes: An array of Mesh pointers. + * \param obmats: An array of pointers to the obmat matrices that transform local + * coordinates to global ones. It is allowed for the pointers to be null, meaning the + * transformation is the identity. + * \param material_remaps: An array of pointers to arrays of maps from material slot numbers in the + * corresponding mesh to the material slot in the first mesh. It is OK for material_remaps or any + * of its constituent arrays to be empty. + */ Mesh *direct_mesh_boolean(blender::Span<const Mesh *> meshes, blender::Span<const float4x4 *> obmats, const float4x4 &target_transform, blender::Span<blender::Array<short>> material_remaps, - const bool use_self, - const bool hole_tolerant, - const int boolean_mode); + bool use_self, + bool hole_tolerant, + int boolean_mode); } // namespace blender::meshintersect diff --git a/source/blender/blenkernel/BKE_mesh_fair.h b/source/blender/blenkernel/BKE_mesh_fair.h index 2d5c85d4129..c4c1af054f0 100644 --- a/source/blender/blenkernel/BKE_mesh_fair.h +++ b/source/blender/blenkernel/BKE_mesh_fair.h @@ -42,14 +42,14 @@ typedef enum eMeshFairingDepth { * the vertex should be modified by fairing. */ void BKE_bmesh_prefair_and_fair_vertices(struct BMesh *bm, bool *affect_vertices, - const eMeshFairingDepth depth); + eMeshFairingDepth depth); /* This function can optionally use the MVert coordinates of deform_mverts to read and write the * fairing result. When NULL, the function will use mesh->mverts directly. */ void BKE_mesh_prefair_and_fair_vertices(struct Mesh *mesh, struct MVert *deform_mverts, bool *affect_vertices, - const eMeshFairingDepth depth); + eMeshFairingDepth depth); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_mesh_iterators.h b/source/blender/blenkernel/BKE_mesh_iterators.h index a65f25ee182..b28465fc41e 100644 --- a/source/blender/blenkernel/BKE_mesh_iterators.h +++ b/source/blender/blenkernel/BKE_mesh_iterators.h @@ -31,14 +31,16 @@ typedef enum MeshForeachFlag { MESH_FOREACH_USE_NORMAL = (1 << 0), } MeshForeachFlag; -void BKE_mesh_foreach_mapped_vert(struct Mesh *mesh, - void (*func)(void *userData, - int index, - const float co[3], - const float no_f[3], - const short no_s[3]), - void *userData, - MeshForeachFlag flag); +void BKE_mesh_foreach_mapped_vert( + struct Mesh *mesh, + void (*func)(void *userData, int index, const float co[3], const float no[3]), + void *userData, + MeshForeachFlag flag); +/** + * 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( struct Mesh *mesh, int tot_edges, @@ -63,9 +65,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( void *userData, MeshForeachFlag flag); -void BKE_mesh_foreach_mapped_vert_coords_get(struct Mesh *me_eval, - float (*r_cos)[3], - const int totcos); +void BKE_mesh_foreach_mapped_vert_coords_get(struct Mesh *me_eval, float (*r_cos)[3], int totcos); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index 0518f303744..48669278e23 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -100,11 +100,16 @@ UvVertMap *BKE_mesh_uv_vert_map_create(const struct MPoly *mpoly, unsigned int totpoly, unsigned int totvert, const float limit[2], - const bool selected, - const bool use_winding); + bool selected, + bool use_winding); UvMapVert *BKE_mesh_uv_vert_map_get_vert(UvVertMap *vmap, unsigned int v); void BKE_mesh_uv_vert_map_free(UvVertMap *vmap); +/** + * Generates a map where the key is the vertex and the value + * is a list of polys that use that vertex as a corner. + * The lists are allocated from one memory pool. + */ void BKE_mesh_vert_poly_map_create(MeshElemMap **r_map, int **r_mem, const struct MPoly *mpoly, @@ -112,6 +117,11 @@ void BKE_mesh_vert_poly_map_create(MeshElemMap **r_map, int totvert, int totpoly, int totloop); +/** + * Generates a map where the key is the vertex and the value + * is a list of loops that use that vertex as a corner. + * The lists are allocated from one memory pool. + */ void BKE_mesh_vert_loop_map_create(MeshElemMap **r_map, int **r_mem, const struct MPoly *mpoly, @@ -119,45 +129,84 @@ void BKE_mesh_vert_loop_map_create(MeshElemMap **r_map, int totvert, int totpoly, int totloop); +/** + * Generates a map where the key is the edge and the value + * is a list of looptris that use that edge. + * The lists are allocated from one memory pool. + */ void BKE_mesh_vert_looptri_map_create(MeshElemMap **r_map, int **r_mem, const struct MVert *mvert, - const int totvert, + int totvert, const struct MLoopTri *mlooptri, - const int totlooptri, + int totlooptri, const struct MLoop *mloop, - const int totloop); + int totloop); +/** + * Generates a map where the key is the vertex and the value + * is a list of edges that use that vertex as an endpoint. + * The lists are allocated from one memory pool. + */ void BKE_mesh_vert_edge_map_create( MeshElemMap **r_map, int **r_mem, const struct MEdge *medge, int totvert, int totedge); +/** + * A version of #BKE_mesh_vert_edge_map_create that references connected vertices directly + * (not their edges). + */ void BKE_mesh_vert_edge_vert_map_create( MeshElemMap **r_map, int **r_mem, const struct MEdge *medge, int totvert, int totedge); +/** + * Generates a map where the key is the edge and the value is a list of loops that use that edge. + * Loops indices of a same poly are contiguous and in winding order. + * The lists are allocated from one memory pool. + */ void BKE_mesh_edge_loop_map_create(MeshElemMap **r_map, int **r_mem, const struct MEdge *medge, - const int totedge, + int totedge, const struct MPoly *mpoly, - const int totpoly, + int totpoly, const struct MLoop *mloop, - const int totloop); + int totloop); +/** + * Generates a map where the key is the edge and the value + * is a list of polygons that use that edge. + * The lists are allocated from one memory pool. + */ void BKE_mesh_edge_poly_map_create(MeshElemMap **r_map, int **r_mem, const struct MEdge *medge, - const int totedge, + int totedge, const struct MPoly *mpoly, - const int totpoly, + int totpoly, const struct MLoop *mloop, - const int totloop); -void BKE_mesh_origindex_map_create(MeshElemMap **r_map, - int **r_mem, - const int totsource, - const int *final_origindex, - const int totfinal); + int totloop); +/** + * This function creates a map so the source-data (vert/edge/loop/poly) + * can loop over the destination data (using the destination arrays origindex). + * + * This has the advantage that it can operate on any data-types. + * + * \param totsource: The total number of elements that \a final_origindex points to. + * \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 custom-data. + * This would allow an MPoly to loop over its tessfaces. + */ +void BKE_mesh_origindex_map_create( + MeshElemMap **r_map, int **r_mem, int totsource, const int *final_origindex, int totfinal); +/** + * A version of #BKE_mesh_origindex_map_create that takes a looptri array. + * Making a poly -> looptri map. + */ void BKE_mesh_origindex_map_create_looptri(MeshElemMap **r_map, int **r_mem, const struct MPoly *mpoly, - const int mpoly_num, + int mpoly_num, const struct MLoopTri *looptri, - const int looptri_num); + int looptri_num); /* islands */ @@ -187,62 +236,87 @@ typedef struct MeshIslandStore { } MeshIslandStore; void BKE_mesh_loop_islands_init(MeshIslandStore *island_store, - const short item_type, - const int items_num, - const short island_type, - const short innercut_type); + short item_type, + int items_num, + short island_type, + short innercut_type); void BKE_mesh_loop_islands_clear(MeshIslandStore *island_store); void BKE_mesh_loop_islands_free(MeshIslandStore *island_store); void BKE_mesh_loop_islands_add(MeshIslandStore *island_store, - const int item_num, + int item_num, const int *items_indices, - const int num_island_items, + int num_island_items, int *island_item_indices, - const int num_innercut_items, + int num_innercut_items, int *innercut_item_indices); typedef bool (*MeshRemapIslandsCalc)(struct MVert *verts, - const int totvert, + int totvert, struct MEdge *edges, - const int totedge, + int totedge, struct MPoly *polys, - const int totpoly, + int totpoly, struct MLoop *loops, - const int totloop, + int totloop, struct MeshIslandStore *r_island_store); /* Above vert/UV mapping stuff does not do what we need here, but does things we do not need here. - * So better keep them separated for now, I think. + * So better keep them separated for now, I think. */ + +/** + * Calculate 'generic' UV islands, i.e. based only on actual geometry data (edge seams), + * not some UV layers coordinates. */ bool BKE_mesh_calc_islands_loop_poly_edgeseam(struct MVert *verts, - const int totvert, + int totvert, struct MEdge *edges, - const int totedge, + int totedge, struct MPoly *polys, - const int totpoly, + int totpoly, struct MLoop *loops, - const int totloop, + int totloop, MeshIslandStore *r_island_store); +/** + * Calculate UV islands. + * + * \note If no MLoopUV layer is passed, we only consider edges tagged as seams as UV boundaries. + * This has the advantages of simplicity, and being valid/common to all UV maps. + * However, it means actual UV islands without matching UV seams will not be handled correctly. + * If a valid UV layer is passed as \a luvs parameter, + * UV coordinates are also used to detect islands boundaries. + * + * \note All this could be optimized. + * Not sure it would be worth the more complex code, though, + * those loops are supposed to be really quick to do. + */ bool BKE_mesh_calc_islands_loop_poly_uvmap(struct MVert *verts, - const int totvert, + int totvert, struct MEdge *edges, - const int totedge, + int totedge, struct MPoly *polys, - const int totpoly, + int totpoly, struct MLoop *loops, - const int totloop, + int totloop, const struct MLoopUV *luvs, MeshIslandStore *r_island_store); +/** + * Calculate smooth groups from sharp edges. + * + * \param r_totgroup: The total number of groups, 1 or more. + * \return Polygon aligned array of group index values (bitflags if use_bitflags is true), + * starting at 1 (0 being used as 'invalid' flag). + * Note it's callers's responsibility to MEM_freeN returned array. + */ int *BKE_mesh_calc_smoothgroups(const struct MEdge *medge, - const int totedge, + int totedge, const struct MPoly *mpoly, - const int totpoly, + int totpoly, const struct MLoop *mloop, - const int totloop, + int totloop, int *r_totgroup, - const bool use_bitflags); + bool use_bitflags); /* use on looptri vertex values */ #define BKE_MESH_TESSTRI_VINDEX_ORDER(_tri, _v) \ diff --git a/source/blender/blenkernel/BKE_mesh_mirror.h b/source/blender/blenkernel/BKE_mesh_mirror.h index 7b230b04410..c77974d6cc1 100644 --- a/source/blender/blenkernel/BKE_mesh_mirror.h +++ b/source/blender/blenkernel/BKE_mesh_mirror.h @@ -40,13 +40,18 @@ struct Mesh *BKE_mesh_mirror_bisect_on_mirror_plane_for_modifier(struct MirrorMo void BKE_mesh_mirror_apply_mirror_on_axis(struct Main *bmain, struct Mesh *mesh, - const int axis, - const float dist); + int axis, + float dist); +/** + * \warning This should _not_ be used to modify original meshes since + * it doesn't handle shape-keys, use #BKE_mesh_mirror_apply_mirror_on_axis instead. + */ struct Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(struct MirrorModifierData *mmd, struct Object *ob, const struct Mesh *mesh, - const int axis); + int axis, + bool use_correct_order_on_merge); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_mesh_remap.h b/source/blender/blenkernel/BKE_mesh_remap.h index 7f8f028c26b..ab07cb0df4e 100644 --- a/source/blender/blenkernel/BKE_mesh_remap.h +++ b/source/blender/blenkernel/BKE_mesh_remap.h @@ -50,10 +50,10 @@ typedef struct MeshPairRemap { } MeshPairRemap; /* Helpers! */ -void BKE_mesh_remap_init(MeshPairRemap *map, const int items_num); +void BKE_mesh_remap_init(MeshPairRemap *map, int items_num); void BKE_mesh_remap_free(MeshPairRemap *map); -void BKE_mesh_remap_item_define_invalid(MeshPairRemap *map, const int index); +void BKE_mesh_remap_item_define_invalid(MeshPairRemap *map, int index); /* TODO: * Add other 'from/to' mapping sources, like e.g. using an UVMap, etc. @@ -155,78 +155,88 @@ enum { }; void BKE_mesh_remap_calc_source_cddata_masks_from_map_modes( - const int vert_mode, - const int edge_mode, - const int loop_mode, - const int poly_mode, + int vert_mode, + int edge_mode, + int loop_mode, + int poly_mode, struct CustomData_MeshMasks *cddata_mask); +/** + * Compute a value of the difference between both given meshes. + * The smaller the result, the better the match. + * + * We return the inverse of the average of the inversed + * shortest distance from each dst vertex to src ones. + * In other words, beyond a certain (relatively small) distance, all differences have more or less + * the same weight in final result, which allows to reduce influence of a few high differences, + * in favor of a global good matching. + */ float BKE_mesh_remap_calc_difference_from_mesh(const struct SpaceTransform *space_transform, const struct MVert *verts_dst, - const int numverts_dst, + int numverts_dst, struct Mesh *me_src); +/** + * Set r_space_transform so that best bbox of dst matches best bbox of src. + */ void BKE_mesh_remap_find_best_match_from_mesh(const struct MVert *verts_dst, - const int numverts_dst, + int numverts_dst, struct Mesh *me_src, struct SpaceTransform *r_space_transform); -void BKE_mesh_remap_calc_verts_from_mesh(const int mode, +void BKE_mesh_remap_calc_verts_from_mesh(int mode, const struct SpaceTransform *space_transform, - const float max_dist, - const float ray_radius, + float max_dist, + float ray_radius, const struct MVert *verts_dst, - const int numverts_dst, - const bool dirty_nors_dst, + int numverts_dst, + bool dirty_nors_dst, struct Mesh *me_src, MeshPairRemap *r_map); -void BKE_mesh_remap_calc_edges_from_mesh(const int mode, +void BKE_mesh_remap_calc_edges_from_mesh(int mode, const struct SpaceTransform *space_transform, - const float max_dist, - const float ray_radius, + float max_dist, + float ray_radius, const struct MVert *verts_dst, - const int numverts_dst, + int numverts_dst, const struct MEdge *edges_dst, - const int numedges_dst, - const bool dirty_nors_dst, + int numedges_dst, + bool dirty_nors_dst, struct Mesh *me_src, MeshPairRemap *r_map); -void BKE_mesh_remap_calc_loops_from_mesh(const int mode, +void BKE_mesh_remap_calc_loops_from_mesh(int mode, const struct SpaceTransform *space_transform, - const float max_dist, - const float ray_radius, + float max_dist, + float ray_radius, + struct Mesh *mesh_dst, struct MVert *verts_dst, - const int numverts_dst, + int numverts_dst, struct MEdge *edges_dst, - const int numedges_dst, + int numedges_dst, struct MLoop *loops_dst, - const int numloops_dst, + int numloops_dst, struct MPoly *polys_dst, - const int numpolys_dst, + int numpolys_dst, struct CustomData *ldata_dst, - struct CustomData *pdata_dst, - const bool use_split_nors_dst, - const float split_angle_dst, - const bool dirty_nors_dst, + bool use_split_nors_dst, + float split_angle_dst, + bool dirty_nors_dst, struct Mesh *me_src, MeshRemapIslandsCalc gen_islands_src, - const float islands_precision_src, + float islands_precision_src, struct MeshPairRemap *r_map); -void BKE_mesh_remap_calc_polys_from_mesh(const int mode, +void BKE_mesh_remap_calc_polys_from_mesh(int mode, const struct SpaceTransform *space_transform, - const float max_dist, - const float ray_radius, + float max_dist, + float ray_radius, + struct Mesh *mesh_dst, struct MVert *verts_dst, - const int numverts_dst, struct MLoop *loops_dst, - const int numloops_dst, struct MPoly *polys_dst, - const int numpolys_dst, - struct CustomData *pdata_dst, - const bool dirty_nors_dst, + int numpolys_dst, struct Mesh *me_src, struct MeshPairRemap *r_map); diff --git a/source/blender/blenkernel/BKE_mesh_runtime.h b/source/blender/blenkernel/BKE_mesh_runtime.h index 3efbef94081..ad86f6d8f25 100644 --- a/source/blender/blenkernel/BKE_mesh_runtime.h +++ b/source/blender/blenkernel/BKE_mesh_runtime.h @@ -41,17 +41,42 @@ struct Mesh; struct Object; struct Scene; -void BKE_mesh_runtime_reset(struct Mesh *mesh); -void BKE_mesh_runtime_reset_on_copy(struct Mesh *mesh, const int flag); +/** + * \brief Initialize the runtime of the given mesh. + * + * Function expects that the runtime is already cleared. + */ +void BKE_mesh_runtime_init_data(struct Mesh *mesh); +/** + * \brief Free all data (and mutexes) inside the runtime of the given mesh. + */ +void BKE_mesh_runtime_free_data(struct Mesh *mesh); +/** + * Clear all pointers which we don't want to be shared on copying the datablock. + * However, keep all the flags which defines what the mesh is (for example, that + * it's deformed only, or that its custom data layers are out of date.) + */ +void BKE_mesh_runtime_reset_on_copy(struct Mesh *mesh, int flag); int BKE_mesh_runtime_looptri_len(const struct Mesh *mesh); void BKE_mesh_runtime_looptri_recalc(struct 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 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); void BKE_mesh_runtime_clear_geometry(struct Mesh *mesh); +/** + * \brief This function clears runtime cache of the given mesh. + * + * Call this function to recalculate runtime data when used. + */ void BKE_mesh_runtime_clear_cache(struct Mesh *mesh); +/* This is a copy of DM_verttri_from_looptri(). */ void BKE_mesh_runtime_verttri_from_looptri(struct MVertTri *r_verttri, const struct MLoop *mloop, const struct MLoopTri *looptri, @@ -61,6 +86,7 @@ void BKE_mesh_runtime_verttri_from_looptri(struct MVertTri *r_verttri, * to a more suitable location when that file is removed. * They should also be renamed to use conventions from BKE, not old DerivedMesh.cc. * For now keep the names similar to avoid confusion. */ + struct Mesh *mesh_get_eval_final(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, @@ -76,12 +102,6 @@ struct Mesh *mesh_create_eval_final(struct Depsgraph *depsgraph, struct Object *ob, const struct CustomData_MeshMasks *dataMask); -struct Mesh *mesh_create_eval_final_index_render(struct Depsgraph *depsgraph, - struct Scene *scene, - struct Object *ob, - const struct CustomData_MeshMasks *dataMask, - int index); - struct Mesh *mesh_create_eval_no_deform(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, @@ -96,9 +116,6 @@ void BKE_mesh_runtime_eval_to_meshkey(struct Mesh *me_deformed, struct KeyBlock *kb); #ifndef NDEBUG -char *BKE_mesh_runtime_debug_info(struct Mesh *me_eval); -void BKE_mesh_runtime_debug_print(struct Mesh *me_eval); -void BKE_mesh_runtime_debug_print_cdlayers(struct CustomData *data); bool BKE_mesh_runtime_is_valid(struct Mesh *me_eval); #endif /* NDEBUG */ diff --git a/source/blender/blenkernel/BKE_mesh_sample.hh b/source/blender/blenkernel/BKE_mesh_sample.hh index 2fbf7372a09..738b768d906 100644 --- a/source/blender/blenkernel/BKE_mesh_sample.hh +++ b/source/blender/blenkernel/BKE_mesh_sample.hh @@ -22,7 +22,7 @@ #include "FN_generic_virtual_array.hh" -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BKE_attribute.h" @@ -44,17 +44,20 @@ 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 { @@ -72,6 +75,7 @@ enum class eAttributeMapMode { class MeshAttributeInterpolator { private: const Mesh *mesh_; + const IndexMask mask_; const Span<float3> positions_; const Span<int> looptri_indices_; @@ -80,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, + AttributeDomain domain, + eAttributeMapMode mode, + const GMutableSpan dst); + void sample_attribute(const ReadAttributeLookup &src_attribute, OutputAttribute &dst_attribute, eAttributeMapMode mode); diff --git a/source/blender/blenkernel/BKE_mesh_tangent.h b/source/blender/blenkernel/BKE_mesh_tangent.h index 96eaa23ce71..320c4e7f36a 100644 --- a/source/blender/blenkernel/BKE_mesh_tangent.h +++ b/source/blender/blenkernel/BKE_mesh_tangent.h @@ -25,38 +25,54 @@ extern "C" { struct ReportList; +/** + * 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! + */ void BKE_mesh_calc_loop_tangent_single_ex(const struct MVert *mverts, - const int numVerts, + int numVerts, const struct MLoop *mloops, float (*r_looptangent)[4], float (*loopnors)[3], const struct MLoopUV *loopuv, - const int numLoops, + int numLoops, const struct MPoly *mpolys, - const int numPolys, + int numPolys, struct ReportList *reports); +/** + * Wrapper around BKE_mesh_calc_loop_tangent_single_ex, which takes care of most boiling code. + * \note + * - There must be a valid loop's CD_NORMALS available. + * - The mesh should be made of only tris and quads! + */ void BKE_mesh_calc_loop_tangent_single(struct Mesh *mesh, const char *uvmap, float (*r_looptangents)[4], struct ReportList *reports); +/** + * See: #BKE_editmesh_loop_tangent_calc (matching logic). + */ void BKE_mesh_calc_loop_tangent_ex(const struct MVert *mvert, const struct MPoly *mpoly, - const uint mpoly_len, + uint mpoly_len, const struct MLoop *mloop, const struct MLoopTri *looptri, - const uint looptri_len, + uint looptri_len, struct CustomData *loopdata, bool calc_active_tangent, const char (*tangent_names)[64], int tangent_names_len, + const float (*vert_normals)[3], const float (*poly_normals)[3], const float (*loop_normals)[3], const float (*vert_orco)[3], /* result */ struct CustomData *loopdata_out, - const uint loopdata_out_len, + uint loopdata_out_len, short *tangent_mask_curr_p); void BKE_mesh_calc_loop_tangents(struct Mesh *me_eval, @@ -71,6 +87,12 @@ void BKE_mesh_add_loop_tangent_named_layer_for_uv(struct CustomData *uv_data, const char *layer_name); #define DM_TANGENT_MASK_ORCO (1 << 9) +/** + * Here we get some useful information such as active uv layer name and + * search if it is already in tangent_names. + * Also, we calculate tangent_mask that works as a descriptor of tangents state. + * If tangent_mask has changed, then recalculate tangents. + */ void BKE_mesh_calc_loop_tangent_step_0(const struct CustomData *loopData, bool calc_active_tangent, const char (*tangent_names)[64], diff --git a/source/blender/blenkernel/BKE_mesh_wrapper.h b/source/blender/blenkernel/BKE_mesh_wrapper.h index 2fe264fd0f7..12e8fd71503 100644 --- a/source/blender/blenkernel/BKE_mesh_wrapper.h +++ b/source/blender/blenkernel/BKE_mesh_wrapper.h @@ -22,6 +22,7 @@ struct BMEditMesh; struct CustomData_MeshMasks; struct Mesh; +struct Object; #ifdef __cplusplus extern "C" { @@ -51,6 +52,8 @@ void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const struct Mesh *me, int vert_coords_len, const float mat[4][4]); +struct Mesh *BKE_mesh_wrapper_ensure_subdivision(const struct Object *ob, struct Mesh *me); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_modifier.h b/source/blender/blenkernel/BKE_modifier.h index 8be563e4c96..80889813b34 100644 --- a/source/blender/blenkernel/BKE_modifier.h +++ b/source/blender/blenkernel/BKE_modifier.h @@ -98,7 +98,7 @@ typedef enum { eModifierTypeFlag_RequiresOriginalData = (1 << 5), /** - * For modifiers that support pointcache, + * For modifiers that support point-cache, * so we can check to see if it has files we need to deal with. */ eModifierTypeFlag_UsesPointCache = (1 << 6), @@ -184,7 +184,7 @@ typedef struct ModifierTypeInfo { * * \param flag: Copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more). */ - void (*copyData)(const struct ModifierData *md, struct ModifierData *target, const int flag); + void (*copyData)(const struct ModifierData *md, struct ModifierData *target, int flag); /********************* Deform modifier functions *********************/ @@ -322,7 +322,7 @@ typedef struct ModifierTypeInfo { * * The dag_eval_mode should be of type eEvaluationMode. */ - bool (*dependsOnTime)(struct Scene *scene, struct ModifierData *md, const int dag_eval_mode); + bool (*dependsOnTime)(struct Scene *scene, struct ModifierData *md, int dag_eval_mode); /** * True when a deform modifier uses normals, the requiredDataMask @@ -403,6 +403,10 @@ void BKE_modifier_init(void); const ModifierTypeInfo *BKE_modifier_get_info(ModifierType type); /* For modifier UI panels. */ + +/** + * Get the idname of the modifier type's panel, which was defined in the #panelRegister callback. + */ void BKE_modifier_type_panel_id(ModifierType type, char *r_idname); void BKE_modifier_panel_expand(struct ModifierData *md); @@ -411,8 +415,11 @@ void BKE_modifier_panel_expand(struct ModifierData *md); */ struct ModifierData *BKE_modifier_new(int type); -void BKE_modifier_free_ex(struct ModifierData *md, const int flag); +void BKE_modifier_free_ex(struct ModifierData *md, int flag); void BKE_modifier_free(struct ModifierData *md); +/** + * Use instead of `BLI_remlink` when the object's active modifier should change. + */ void BKE_modifier_remove_from_list(struct Object *ob, struct ModifierData *md); /* Generate new UUID for the given modifier. */ @@ -420,13 +427,14 @@ void BKE_modifier_session_uuid_generate(struct ModifierData *md); bool BKE_modifier_unique_name(struct ListBase *modifiers, struct ModifierData *md); +/** + * Callback's can use this to avoid copying every member. + */ void BKE_modifier_copydata_generic(const struct ModifierData *md, struct ModifierData *md_dst, - const int flag); + int flag); void BKE_modifier_copydata(struct ModifierData *md, struct ModifierData *target); -void BKE_modifier_copydata_ex(struct ModifierData *md, - struct ModifierData *target, - const int flag); +void BKE_modifier_copydata_ex(struct ModifierData *md, struct ModifierData *target, int flag); 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); @@ -434,9 +442,21 @@ bool BKE_modifier_couldbe_cage(struct Scene *scene, struct ModifierData *md); bool BKE_modifier_is_correctable_deformed(struct ModifierData *md); bool BKE_modifier_is_same_topology(ModifierData *md); bool BKE_modifier_is_non_geometrical(ModifierData *md); +/** + * Check whether is enabled. + * + * \param scene: Current scene, may be NULL, + * in which case `isDisabled` callback of the modifier is never called. + */ bool BKE_modifier_is_enabled(const struct Scene *scene, struct ModifierData *md, int required_mode); +/** + * Check whether given modifier is not local (i.e. from linked data) when the object is a library + * override. + * + * \param md: May be NULL, in which case we consider it as a non-local modifier case. + */ bool BKE_modifier_is_nonlocal_in_liboverride(const struct Object *ob, const struct ModifierData *md); void BKE_modifier_set_error(const struct Object *ob, @@ -451,6 +471,12 @@ void BKE_modifiers_foreach_tex_link(struct Object *ob, TexWalkFunc walk, void *u struct ModifierData *BKE_modifiers_findby_type(const struct Object *ob, ModifierType type); struct ModifierData *BKE_modifiers_findby_name(const struct Object *ob, const char *name); void BKE_modifiers_clear_errors(struct Object *ob); +/** + * used for buttons, to find out if the 'draw deformed in edit-mode option is there. + * + * Also used in transform_conversion.c, to detect crazy-space (2nd arg then is NULL). + * Also used for some mesh tools to give warnings. + */ int BKE_modifiers_get_cage_index(const struct Scene *scene, struct Object *ob, int *r_lastPossibleCageIndex, @@ -461,9 +487,21 @@ bool BKE_modifiers_is_softbody_enabled(struct Object *ob); bool BKE_modifiers_is_cloth_enabled(struct Object *ob); bool BKE_modifiers_is_particle_enabled(struct Object *ob); +/** + * Takes an object and returns its first selected armature, else just its armature. + * This should work for multiple armatures per object. + */ struct Object *BKE_modifiers_is_deformed_by_armature(struct Object *ob); struct Object *BKE_modifiers_is_deformed_by_meshdeform(struct Object *ob); +/** + * Takes an object and returns its first selected lattice, else just its lattice. + * This should work for multiple lattices per object. + */ struct Object *BKE_modifiers_is_deformed_by_lattice(struct Object *ob); +/** + * Takes an object and returns its first selected curve, else just its curve. + * This should work for multiple curves per object. + */ struct Object *BKE_modifiers_is_deformed_by_curve(struct Object *ob); bool BKE_modifiers_uses_multires(struct Object *ob); bool BKE_modifiers_uses_armature(struct Object *ob, struct bArmature *arm); @@ -500,23 +538,36 @@ typedef struct VirtualModifierData { ShapeKeyModifierData smd; } VirtualModifierData; +/** + * This is to include things that are not modifiers in the evaluation of the modifier stack, + * for example parenting to an armature. + */ struct ModifierData *BKE_modifiers_get_virtual_modifierlist(const struct Object *ob, struct VirtualModifierData *data); -/** Ensure modifier correctness when changing ob->data. */ +/** + * Ensure modifier correctness when changing `ob->data`. + */ void BKE_modifiers_test_object(struct Object *ob); -/* here for do_versions */ +/** + * Here for #do_versions. + */ void BKE_modifier_mdef_compact_influences(struct ModifierData *md); +/** + * Initializes `path` with either the blend file or temporary directory. + */ void BKE_modifier_path_init(char *path, int path_maxlen, const char *name); const char *BKE_modifier_path_relbase(struct Main *bmain, struct Object *ob); const char *BKE_modifier_path_relbase_from_global(struct Object *ob); /* Accessors of original/evaluated modifiers. */ -/* For a given modifier data, get corresponding original one. - * If the modifier data is already original, return it as-is. */ +/** + * For a given modifier data, get corresponding original one. + * If the modifier data is already original, return it as-is. + */ struct ModifierData *BKE_modifier_get_original(struct ModifierData *md); struct ModifierData *BKE_modifier_get_evaluated(struct Depsgraph *depsgraph, struct Object *object, @@ -541,8 +592,17 @@ void BKE_modifier_deform_vertsEM(ModifierData *md, float (*vertexCos)[3], int numVerts); +/** + * Get evaluated mesh for other evaluated object, which is used as an operand for the modifier, + * e.g. second operand for boolean modifier. + * Note that modifiers in stack always get fully evaluated COW ID pointers, + * never original ones. Makes things simpler. + * + * \param get_cage_mesh: Return evaluated mesh with only deforming modifiers applied + * (i.e. mesh topology remains the same as original one, a.k.a. 'cage' mesh). + */ struct Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(struct Object *ob_eval, - const bool get_cage_mesh); + bool get_cage_mesh); void BKE_modifier_check_uuids_unique_and_report(const struct Object *object); diff --git a/source/blender/blenkernel/BKE_movieclip.h b/source/blender/blenkernel/BKE_movieclip.h index 067dc694109..9148d5b760f 100644 --- a/source/blender/blenkernel/BKE_movieclip.h +++ b/source/blender/blenkernel/BKE_movieclip.h @@ -35,6 +35,10 @@ struct MovieClipScopes; struct MovieClipUser; struct MovieDistortion; +/** + * Checks if image was already loaded, then returns same image otherwise creates new. + * does not load ibuf itself pass on optional frame for #name images. + */ struct MovieClip *BKE_movieclip_file_add(struct Main *bmain, const char *name); struct MovieClip *BKE_movieclip_file_add_exists_ex(struct Main *bmain, const char *filepath, @@ -44,6 +48,11 @@ void BKE_movieclip_reload(struct Main *bmain, struct MovieClip *clip); void BKE_movieclip_clear_cache(struct MovieClip *clip); void BKE_movieclip_clear_proxy_cache(struct MovieClip *clip); +/** + * Will try to make image buffer usable when originating from the multi-layer source. + * Internally finds a first combined pass and uses that as a buffer. + * Not ideal, but is better than a complete empty buffer. + */ void BKE_movieclip_convert_multilayer_ibuf(struct ImBuf *ibuf); struct ImBuf *BKE_movieclip_get_ibuf(struct MovieClip *clip, struct MovieClipUser *user); @@ -75,11 +84,18 @@ void BKE_movieclip_update_scopes(struct MovieClip *clip, struct MovieClipUser *user, struct MovieClipScopes *scopes); +/** + * Get segments of cached frames. useful for debugging cache policies. + */ void BKE_movieclip_get_cache_segments(struct MovieClip *clip, struct MovieClipUser *user, int *r_totseg, int **r_points); +/** + * \note currently used by proxy job for movies, threading happens within single frame + * (meaning scaling shall be threaded). + */ void BKE_movieclip_build_proxy_frame(struct MovieClip *clip, int clip_flag, struct MovieDistortion *distortion, @@ -88,6 +104,10 @@ void BKE_movieclip_build_proxy_frame(struct MovieClip *clip, int build_count, bool undistorted); +/** + * \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(struct MovieClip *clip, struct ImBuf *ibuf, struct MovieDistortion *distortion, @@ -95,6 +115,7 @@ void BKE_movieclip_build_proxy_frame_for_ibuf(struct MovieClip *clip, int *build_sizes, int build_count, bool undistorted); +bool BKE_movieclip_proxy_enabled(struct MovieClip *clip); float BKE_movieclip_remap_scene_to_clip_frame(const struct MovieClip *clip, float framenr); float BKE_movieclip_remap_clip_to_scene_frame(const struct MovieClip *clip, float framenr); @@ -103,8 +124,10 @@ void BKE_movieclip_filename_for_frame(struct MovieClip *clip, struct MovieClipUser *user, char *name); -/* Read image buffer from the given movie clip without acquiring the `LOCK_MOVIECLIP` lock. - * Used by a prefetch job which takes care of creating a local copy of the clip. */ +/** + * Read image buffer from the given movie clip without acquiring the #LOCK_MOVIECLIP lock. + * Used by a prefetch job which takes care of creating a local copy of the clip. + */ struct ImBuf *BKE_movieclip_anim_ibuf_for_frame_no_lock(struct MovieClip *clip, struct MovieClipUser *user); @@ -125,10 +148,10 @@ void BKE_movieclip_eval_update(struct Depsgraph *depsgraph, struct MovieClip *clip); void BKE_movieclip_eval_selection_update(struct Depsgraph *depsgraph, struct MovieClip *clip); -/* caching flags */ +/** Caching flags. */ #define MOVIECLIP_CACHE_SKIP (1 << 0) -/* postprocessing flags */ +/** Post-processing flags. */ #define MOVIECLIP_DISABLE_RED (1 << 0) #define MOVIECLIP_DISABLE_GREEN (1 << 1) #define MOVIECLIP_DISABLE_BLUE (1 << 2) diff --git a/source/blender/blenkernel/BKE_multires.h b/source/blender/blenkernel/BKE_multires.h index 11bfc4b2b3a..504771fa733 100644 --- a/source/blender/blenkernel/BKE_multires.h +++ b/source/blender/blenkernel/BKE_multires.h @@ -45,7 +45,9 @@ struct MLoopTri; struct MPoly; struct MVert; -/* Delete mesh mdisps and grid paint masks */ +/** + * Delete mesh mdisps and grid paint masks. + */ void multires_customdata_delete(struct Mesh *me); void multires_set_tot_level(struct Object *ob, struct MultiresModifierData *mmd, int lvl); @@ -62,6 +64,9 @@ void multires_force_external_reload(struct Object *object); void multires_modifier_update_mdisps(struct DerivedMesh *dm, struct Scene *scene); void multires_modifier_update_hidden(struct DerivedMesh *dm); +/** + * Reset the multi-res levels to match the number of mdisps. + */ void multiresModifier_set_levels_from_disps(struct MultiresModifierData *mmd, struct Object *ob); typedef enum { @@ -79,6 +84,10 @@ struct DerivedMesh *multires_make_derived_from_derived(struct DerivedMesh *dm, struct MultiresModifierData *find_multires_modifier_before(struct Scene *scene, struct ModifierData *lastmd); +/** + * used for applying scale on mdisps layer and syncing subdivide levels when joining objects. + * \param use_first: return first multi-res modifier if all multi-res'es are disabled. + */ struct MultiresModifierData *get_multires_modifier(struct Scene *scene, struct Object *ob, bool use_first); @@ -88,18 +97,25 @@ int multires_get_level(const struct Scene *scene, bool render, bool ignore_simplify); -/* Creates mesh with multires modifier applied on current object's deform mesh. */ +/** + * Creates mesh with multi-res modifier applied on current object's deform mesh. + */ struct Mesh *BKE_multires_create_mesh(struct Depsgraph *depsgraph, struct Object *object, struct MultiresModifierData *mmd); -/* Get coordinates of a deformed base mesh which is an input to the given multires modifier. - * NOTE: The modifiers will be re-evaluated. */ +/** + * Get coordinates of a deformed base mesh which is an input to the given multi-res modifier. + * \note The modifiers will be re-evaluated. + */ float (*BKE_multires_create_deformed_base_mesh_vert_coords(struct Depsgraph *depsgraph, struct Object *object, struct MultiresModifierData *mmd, int *r_num_deformed_verts))[3]; +/** + * \param direction: 1 for delete higher, 0 for lower (not implemented yet). + */ void multiresModifier_del_levels(struct MultiresModifierData *mmd, struct Scene *scene, struct Object *object, @@ -112,6 +128,10 @@ int multiresModifier_rebuild_subdiv(struct Depsgraph *depsgraph, struct MultiresModifierData *mmd, int rebuild_limit, bool switch_view_to_lower_level); +/** + * If `ob_src` and `ob_dst` both have multi-res modifiers, + * synchronize them such that `ob_dst` has the same total number of levels as `ob_src`. + */ void multiresModifier_sync_levels_ex(struct Object *ob_dst, struct MultiresModifierData *mmd_src, struct MultiresModifierData *mmd_dst); @@ -128,22 +148,36 @@ void multiresModifier_prepare_join(struct Depsgraph *depsgraph, int multires_mdisp_corners(struct MDisps *s); -/* update multires data after topology changing */ +/** + * Update multi-res data after topology changing. + */ void multires_topology_changed(struct Mesh *me); +/** + * Makes sure data from an external file is fully read. + * + * Since the multi-res data files only contain displacement vectors without knowledge about + * subdivision level some extra work is needed. Namely make is to all displacement grids have + * proper level and number of displacement vectors set. + */ void multires_ensure_external_read(struct Mesh *mesh, int top_level); void multiresModifier_ensure_external_read(struct Mesh *mesh, const struct MultiresModifierData *mmd); /**** interpolation stuff ****/ -void old_mdisps_bilinear(float out[3], float (*disps)[3], const int st, float u, float v); +/* Adapted from `sculptmode.c` */ + +void old_mdisps_bilinear(float out[3], float (*disps)[3], int st, float u, float v); +/** + * Find per-corner coordinate with given per-face UV coord. + */ int mdisp_rot_face_to_crn(struct MVert *mvert, struct MPoly *mpoly, struct MLoop *mloop, const struct MLoopTri *lt, - const int face_side, - const float u, - const float v, + int face_side, + float u, + float v, float *x, float *y); @@ -153,7 +187,13 @@ bool multiresModifier_reshapeFromVertcos(struct Depsgraph *depsgraph, struct Object *object, struct MultiresModifierData *mmd, const float (*vert_coords)[3], - const int num_vert_coords); + int num_vert_coords); +/** + * Returns truth on success, false otherwise. + * + * This function might fail in cases like source and destination not having + * matched amount of vertices. + */ bool multiresModifier_reshapeFromObject(struct Depsgraph *depsgraph, struct MultiresModifierData *mmd, struct Object *dst, @@ -162,11 +202,11 @@ bool multiresModifier_reshapeFromDeformModifier(struct Depsgraph *depsgraph, struct Object *ob, struct MultiresModifierData *mmd, struct ModifierData *deform_md); -bool multiresModifier_reshapeFromCCG(const int tot_level, +bool multiresModifier_reshapeFromCCG(int tot_level, struct Mesh *coarse_mesh, struct SubdivCCG *subdiv_ccg); -/* Subdivide multires displacement once. */ +/* Subdivide multi-res displacement once. */ typedef enum eMultiresSubdivideModeType { MULTIRES_SUBDIVIDE_CATMULL_CLARK, @@ -176,16 +216,18 @@ typedef enum eMultiresSubdivideModeType { void multiresModifier_subdivide(struct Object *object, struct MultiresModifierData *mmd, - const eMultiresSubdivideModeType mode); + eMultiresSubdivideModeType mode); void multires_subdivide_create_tangent_displacement_linear_grids(struct Object *object, struct MultiresModifierData *mmd); -/* Subdivide displacement to the given level. - * If level is lower than the current top level nothing happens. */ +/** + * Subdivide displacement to the given level. + * If level is lower than the current top level nothing happens. + */ void multiresModifier_subdivide_to_level(struct Object *object, struct MultiresModifierData *mmd, - const int top_level, - const eMultiresSubdivideModeType mode); + int top_level, + eMultiresSubdivideModeType mode); /* Subdivision integration, defined in multires_subdiv.c */ @@ -200,27 +242,29 @@ void BKE_multires_subdiv_mesh_settings_init(struct SubdivToMeshSettings *mesh_se const struct Scene *scene, const struct Object *object, const struct MultiresModifierData *mmd, - const bool use_render_params, - const bool ignore_simplify, - const bool ignore_control_edges); + bool use_render_params, + bool ignore_simplify, + bool ignore_control_edges); /* General helpers. */ -/* For a given partial derivatives of a ptex face get tangent matrix for - * displacement. +/** + * For a given partial derivatives of a PTEX face get tangent matrix for displacement. * * Corner needs to be known to properly "rotate" partial derivatives when the - * matrix is being constructed for quad. For non-quad the corner is to be set - * to 0. */ + * matrix is being constructed for quad. For non-quad the corner is to be set to 0. + */ BLI_INLINE void BKE_multires_construct_tangent_matrix(float tangent_matrix[3][3], const float dPdu[3], const float dPdv[3], - const int corner); + int corner); /* Versioning. */ -/* Convert displacement which is stored for simply-subdivided mesh to a Catmull-Clark - * subdivided mesh. */ +/** + * Convert displacement which is stored for simply-subdivided mesh to a Catmull-Clark + * subdivided mesh. + */ void multires_do_versions_simple_to_catmull_clark(struct Object *object, struct MultiresModifierData *mmd); diff --git a/source/blender/blenkernel/BKE_nla.h b/source/blender/blenkernel/BKE_nla.h index cf8848fe607..61431547bfb 100644 --- a/source/blender/blenkernel/BKE_nla.h +++ b/source/blender/blenkernel/BKE_nla.h @@ -46,106 +46,289 @@ struct PropertyRNA; /* ----------------------------- */ /* Data Management */ +/** + * Remove the given NLA strip from the NLA track it occupies, free the strip's data, + * and the strip itself. + */ void BKE_nlastrip_free(ListBase *strips, struct NlaStrip *strip, bool do_id_user); +/** + * Remove the given NLA track from the set of NLA tracks, free the track's data, + * and the track itself. + */ void BKE_nlatrack_free(ListBase *tracks, struct NlaTrack *nlt, bool do_id_user); +/** + * Free the elements of type NLA Tracks provided in the given list, but do not free + * the list itself since that is not free-standing + */ void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user); +/** + * Copy NLA strip + * + * \param use_same_action: When true, the existing action is used (instead of being duplicated) + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ struct NlaStrip *BKE_nlastrip_copy(struct Main *bmain, struct NlaStrip *strip, - const bool use_same_action, - const int flag); + bool use_same_action, + int flag); +/** + * Copy a single NLA Track. + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ struct NlaTrack *BKE_nlatrack_copy(struct Main *bmain, struct NlaTrack *nlt, - const bool use_same_actions, - const int flag); -void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, const ListBase *src, const int flag); + bool use_same_actions, + int flag); +/** + * Copy all NLA data. + * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... + * flags in BKE_lib_id.h + */ +void BKE_nla_tracks_copy(struct Main *bmain, ListBase *dst, const ListBase *src, int flag); -/* Copy NLA tracks from #adt_source to #adt_dest, and update the active track/strip pointers to - * point at those copies. */ +/** + * Copy NLA tracks from #adt_source to #adt_dest, and update the active track/strip pointers to + * point at those copies. + */ void BKE_nla_tracks_copy_from_adt(struct Main *bmain, struct AnimData *adt_dest, const struct AnimData *adt_source, int flag); +/** + * Add a NLA Track to the given AnimData. + * \param prev: NLA-Track to add the new one after. + */ struct NlaTrack *BKE_nlatrack_add(struct AnimData *adt, struct NlaTrack *prev, bool is_liboverride); +/** + * Create a NLA Strip referencing the given Action. + */ struct NlaStrip *BKE_nlastrip_new(struct bAction *act); +/** + * Add new NLA-strip to the top of the NLA stack - i.e. + * into the last track if space, or a new one otherwise. + */ struct NlaStrip *BKE_nlastack_add_strip(struct AnimData *adt, struct bAction *act, - const bool is_liboverride); + bool is_liboverride); +/** + * Add a NLA Strip referencing the given speaker's sound. + */ struct NlaStrip *BKE_nla_add_soundstrip(struct Main *bmain, struct Scene *scene, struct Speaker *speaker); +/** + * Callback used by lib_query to walk over all ID usages + * (mimics `foreach_id` callback of #IDTypeInfo structure). + */ void BKE_nla_strip_foreach_id(struct NlaStrip *strip, struct LibraryForeachIDData *data); /* ----------------------------- */ /* API */ +/** + * Check if there is any space in the given list to add the given strip. + */ bool BKE_nlastrips_has_space(ListBase *strips, float start, float end); +/** + * Rearrange the strips in the track so that they are always in order + * (usually only needed after a strip has been moved) + */ void BKE_nlastrips_sort_strips(ListBase *strips); +/** + * Add the given NLA-Strip to the given list of strips, assuming that it + * isn't currently a member of another list + */ bool BKE_nlastrips_add_strip(ListBase *strips, struct NlaStrip *strip); +/** + * Convert 'islands' (i.e. continuous string of) selected strips to be + * contained within 'Meta-Strips' which act as strips which contain strips. + * + * \param is_temp: are the meta-strips to be created 'temporary' ones used for transforms? + */ void BKE_nlastrips_make_metas(ListBase *strips, bool is_temp); +/** + * Remove meta-strips (i.e. flatten the list of strips) from the top-level of the list of strips. + * + * \param only_sel: only consider selected meta-strips, otherwise all meta-strips are removed + * \param only_temp: only remove the 'temporary' meta-strips used for transforms + */ void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp); +/** + * Split a meta-strip into a set of normal strips. + */ void BKE_nlastrips_clear_metastrip(ListBase *strips, struct NlaStrip *strip); +/** + * Add the given NLA-Strip to the given Meta-Strip, assuming that the + * strip isn't attached to any list of strips + */ bool BKE_nlameta_add_strip(struct NlaStrip *mstrip, struct NlaStrip *strip); +/** + * Adjust the settings of NLA-Strips contained within a Meta-Strip (recursively), + * until the Meta-Strips children all fit within the Meta-Strip's new dimensions + */ void BKE_nlameta_flush_transforms(struct NlaStrip *mstrip); /* ............ */ +/** + * Find the active NLA-track for the given stack. + */ struct NlaTrack *BKE_nlatrack_find_active(ListBase *tracks); +/** + * Make the given NLA-track the active one for the given stack. If no track is provided, + * this function can be used to simply deactivate all the NLA tracks in the given stack too. + */ void BKE_nlatrack_set_active(ListBase *tracks, struct NlaTrack *nlt); +/** + * Get the NLA Track that the active action/action strip comes from, + * since this info is not stored in AnimData. It also isn't as simple + * as just using the active track, since multiple tracks may have been + * entered at the same time. + */ struct NlaTrack *BKE_nlatrack_find_tweaked(struct AnimData *adt); +/** + * Toggle the 'solo' setting for the given NLA-track, making sure that it is the only one + * that has this status in its AnimData block. + */ void BKE_nlatrack_solo_toggle(struct AnimData *adt, struct NlaTrack *nlt); +/** + * Check if there is any space in the given track to add a strip of the given length. + */ bool BKE_nlatrack_has_space(struct NlaTrack *nlt, float start, float end); +/** + * Rearrange the strips in the track so that they are always in order + * (usually only needed after a strip has been moved). + */ void BKE_nlatrack_sort_strips(struct NlaTrack *nlt); -bool BKE_nlatrack_add_strip(struct NlaTrack *nlt, - struct NlaStrip *strip, - const bool is_liboverride); +/** + * Add the given NLA-Strip to the given NLA-Track, assuming that it + * isn't currently attached to another one. + */ +bool BKE_nlatrack_add_strip(struct NlaTrack *nlt, struct NlaStrip *strip, bool is_liboverride); +/** + * Get the extents of the given NLA-Track including gaps between strips, + * returning whether this succeeded or not + */ bool BKE_nlatrack_get_bounds(struct NlaTrack *nlt, float bounds[2]); - +/** + * Check whether given NLA track is not local (i.e. from linked data) when the object is a library + * override. + * + * \param nlt: May be NULL, in which case we consider it as a non-local track case. + */ bool BKE_nlatrack_is_nonlocal_in_liboverride(const struct ID *id, const struct NlaTrack *nlt); /* ............ */ +/** + * Find the active NLA-strip within the given track. + */ struct NlaStrip *BKE_nlastrip_find_active(struct NlaTrack *nlt); +/** + * Make the given NLA-Strip the active one within the given block. + */ void BKE_nlastrip_set_active(struct AnimData *adt, struct NlaStrip *strip); +/** + * Does the given NLA-strip fall within the given bounds (times)?. + */ bool BKE_nlastrip_within_bounds(struct NlaStrip *strip, float min, float max); +/** + * Recalculate the start and end frames for the current strip, after changing + * the extents of the action or the mapping (repeats or scale factor) info. + */ void BKE_nlastrip_recalculate_bounds(struct NlaStrip *strip); +/** + * Recalculate the start and end frames for the strip to match the bounds of its action such that + * the overall NLA animation result is unchanged. + */ void BKE_nlastrip_recalculate_bounds_sync_action(struct NlaStrip *strip); +/** + * Find (and set) a unique name for a strip from the whole AnimData block + * Uses a similar method to the BLI method, but is implemented differently + * as we need to ensure that the name is unique over several lists of tracks, + * not just a single track. + */ void BKE_nlastrip_validate_name(struct AnimData *adt, struct NlaStrip *strip); /* ............ */ +/** + * Check if the given NLA-Track has any strips with own F-Curves. + */ bool BKE_nlatrack_has_animated_strips(struct NlaTrack *nlt); +/** + * Check if given NLA-Tracks have any strips with own F-Curves. + */ bool BKE_nlatracks_have_animated_strips(ListBase *tracks); +/** + * Validate the NLA-Strips 'control' F-Curves based on the flags set. + */ void BKE_nlastrip_validate_fcurves(struct NlaStrip *strip); +/** + * Check if the given RNA pointer + property combo should be handled by + * NLA strip curves or not. + */ bool BKE_nlastrip_has_curves_for_property(const struct PointerRNA *ptr, const struct PropertyRNA *prop); +/** + * Ensure that auto-blending and other settings are set correctly. + */ void BKE_nla_validate_state(struct AnimData *adt); /* ............ */ +/** + * Check if an action is "stashed" in the NLA already + * + * The criteria for this are: + * 1) The action in question lives in a "stash" track. + * 2) We only check first-level strips. That is, we will not check inside meta strips. + */ bool BKE_nla_action_is_stashed(struct AnimData *adt, struct bAction *act); -bool BKE_nla_action_stash(struct AnimData *adt, const bool is_liboverride); +/** + * "Stash" an action (i.e. store it as a track/layer in the NLA, but non-contributing) + * to retain it in the file for future uses. + */ +bool BKE_nla_action_stash(struct AnimData *adt, bool is_liboverride); /* ............ */ -void BKE_nla_action_pushdown(struct AnimData *adt, const bool is_liboverride); +/** + * For the given AnimData block, add the active action to the NLA + * stack (i.e. 'push-down' action). The UI should only allow this + * for normal editing only (i.e. not in edit-mode for some strip's action), + * so no checks for this are performed. + * + * TODO: maybe we should have checks for this too. + */ +void BKE_nla_action_pushdown(struct AnimData *adt, bool is_liboverride); +/** + * Find the active strip + track combination, and set them up as the tweaking track, + * and return if successful or not. + */ bool BKE_nla_tweakmode_enter(struct AnimData *adt); +/** + * Exit tweak-mode for this AnimData block. + */ void BKE_nla_tweakmode_exit(struct AnimData *adt); /* ----------------------------- */ @@ -163,6 +346,13 @@ enum eNlaTime_ConvertModes { NLATIME_CONVERT_MAP, }; +/** + * Non clipped mapping for strip-time <-> global time: + * `mode = eNlaTime_ConvertModes -> NLATIME_CONVERT_*` + * + * Public API method - perform this mapping using the given AnimData block + * and perform any necessary sanity checks on the value + */ float BKE_nla_tweakedit_remap(struct AnimData *adt, float cframe, short mode); /* ----------------------------- */ diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 76a3e75d285..16d8ba2e6dd 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -34,6 +34,11 @@ #include "RNA_types.h" #ifdef __cplusplus +# include "BLI_map.hh" +# include "BLI_string_ref.hh" +#endif + +#ifdef __cplusplus extern "C" { #endif @@ -114,6 +119,7 @@ namespace nodes { class NodeMultiFunctionBuilder; class GeoNodeExecParams; class NodeDeclarationBuilder; +class GatherLinkSearchOpParams; } // namespace nodes namespace fn { class CPPType; @@ -121,23 +127,28 @@ class MFDataType; } // namespace fn } // namespace blender +using CPPTypeHandle = blender::fn::CPPType; 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 SocketGetGeometryNodesCPPTypeFunction = const blender::fn::CPPType *(*)(); using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket &socket, void *r_value); +/* Adds socket link operations that are specific to this node type. */ +using NodeGatherSocketLinkOperationsFunction = + void (*)(blender::nodes::GatherLinkSearchOpParams ¶ms); + #else typedef void *NodeMultiFunctionBuildFunction; typedef void *NodeGeometryExecFunction; typedef void *NodeDeclareFunction; +typedef void *NodeGatherSocketLinkOperationsFunction; typedef void *SocketGetCPPTypeFunction; typedef void *SocketGetGeometryNodesCPPTypeFunction; typedef void *SocketGetGeometryNodesCPPValueFunction; typedef void *SocketGetCPPValueFunction; +typedef struct CPPTypeHandle CPPTypeHandle; #endif /** @@ -164,20 +175,20 @@ typedef struct bNodeSocketType { void (*interface_draw)(struct bContext *C, struct uiLayout *layout, struct PointerRNA *ptr); void (*interface_draw_color)(struct bContext *C, struct PointerRNA *ptr, float *r_color); void (*interface_register_properties)(struct bNodeTree *ntree, - struct bNodeSocket *stemp, + struct bNodeSocket *interface_socket, struct StructRNA *data_srna); void (*interface_init_socket)(struct bNodeTree *ntree, - struct bNodeSocket *stemp, + const struct bNodeSocket *interface_socket, struct bNode *node, struct bNodeSocket *sock, const char *data_path); void (*interface_verify_socket)(struct bNodeTree *ntree, - struct bNodeSocket *stemp, + const struct bNodeSocket *interface_socket, struct bNode *node, struct bNodeSocket *sock, const char *data_path); void (*interface_from_socket)(struct bNodeTree *ntree, - struct bNodeSocket *stemp, + struct bNodeSocket *interface_socket, struct bNode *node, struct bNodeSocket *sock); @@ -197,11 +208,11 @@ typedef struct bNodeSocketType { void (*free_self)(struct bNodeSocketType *stype); /* Return the CPPType of this socket. */ - SocketGetCPPTypeFunction get_base_cpp_type; + const CPPTypeHandle *base_cpp_type; /* Get the value of this socket in a generic way. */ SocketGetCPPValueFunction get_base_cpp_value; /* Get geometry nodes cpp type. */ - SocketGetGeometryNodesCPPTypeFunction get_geometry_nodes_cpp_type; + const CPPTypeHandle *geometry_nodes_cpp_type; /* Get geometry nodes cpp value. */ SocketGetGeometryNodesCPPValueFunction get_geometry_nodes_cpp_value; } bNodeSocketType; @@ -229,8 +240,6 @@ typedef int (*NodeGPUExecFunction)(struct GPUMaterial *mat, * implementing the node behavior. */ typedef struct bNodeType { - void *next, *prev; - char idname[64]; /* identifier name */ int type; @@ -247,18 +256,6 @@ typedef struct bNodeType { char storagename[64]; /* struct name for DNA */ - /* Main draw function for the node */ - void (*draw_nodetype)(const struct bContext *C, - struct ARegion *region, - struct SpaceNode *snode, - struct bNodeTree *ntree, - struct bNode *node, - bNodeInstanceKey key); - /* Updates the node geometry attributes according to internal state before actual drawing */ - void (*draw_nodetype_prepare)(const struct bContext *C, - struct bNodeTree *ntree, - struct bNode *node); - /* Draw the option buttons on the node */ void (*draw_buttons)(struct uiLayout *, struct bContext *C, struct PointerRNA *ptr); /* Additional parameters in the side panel */ @@ -272,13 +269,10 @@ typedef struct bNodeType { * Optional custom label function for the node header. * \note Used as a fallback when #bNode.label isn't set. */ - void (*labelfunc)(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen); - /** Optional custom resize handle polling. */ - int (*resize_area_func)(struct bNode *node, int x, int y); - /** Optional selection area polling. */ - int (*select_area_func)(struct bNode *node, int x, int y); - /** Optional tweak area polling (for grabbing). */ - int (*tweak_area_func)(struct bNode *node, int x, int y); + void (*labelfunc)(const struct bNodeTree *ntree, + const struct bNode *node, + char *label, + int maxlen); /** Called when the node is updated in the editor. */ void (*updatefunc)(struct bNodeTree *ntree, struct bNode *node); @@ -301,7 +295,7 @@ typedef struct bNodeType { /** * Can this node type be added to a node tree? - * \param r_disabled_hint: Optional hint to display in the UI when the poll fails. + * \param r_disabled_hint: Hint to display in the UI when the poll fails. * The callback can set this to a static string without having to * null-check it (or without setting it to null if it's not used). * The caller must pass a valid `const char **` and null-initialize it @@ -318,8 +312,6 @@ typedef struct bNodeType { /* optional handling of link insertion */ void (*insert_link)(struct bNodeTree *ntree, struct bNode *node, struct bNodeLink *link); - /* Update the internal links list, for muting and disconnect operators. */ - void (*update_internal_links)(struct bNodeTree *, struct bNode *node); void (*free_self)(struct bNodeType *ntype); @@ -339,6 +331,20 @@ typedef struct bNodeType { /* 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; + + /** + * Add to the list of search names and operations gathered by node link drag searching. + * Usually it isn't necessary to override the default behavior here, but a node type can have + * custom behavior here like adding custom search items. + */ + NodeGatherSocketLinkOperationsFunction gather_link_search_ops; + + /** True when the node cannot be muted. */ + bool no_muting; /* RNA integration */ ExtensionRNA rna_ext; @@ -351,22 +357,11 @@ typedef struct bNodeType { #define NODE_CLASS_OP_VECTOR 4 #define NODE_CLASS_OP_FILTER 5 #define NODE_CLASS_GROUP 6 -// #define NODE_CLASS_FILE 7 #define NODE_CLASS_CONVERTER 8 #define NODE_CLASS_MATTE 9 #define NODE_CLASS_DISTORT 10 -// #define NODE_CLASS_OP_DYNAMIC 11 /* deprecated */ #define NODE_CLASS_PATTERN 12 #define NODE_CLASS_TEXTURE 13 -// #define NODE_CLASS_EXECUTION 14 -// #define NODE_CLASS_GETDATA 15 -// #define NODE_CLASS_SETDATA 16 -// #define NODE_CLASS_MATH 17 -// #define NODE_CLASS_MATH_VECTOR 18 -// #define NODE_CLASS_MATH_ROTATION 19 -// #define NODE_CLASS_PARTICLES 25 -// #define NODE_CLASS_TRANSFORM 30 -// #define NODE_CLASS_COMBINE 31 #define NODE_CLASS_SCRIPT 32 #define NODE_CLASS_INTERFACE 33 #define NODE_CLASS_SHADER 40 @@ -374,12 +369,6 @@ typedef struct bNodeType { #define NODE_CLASS_ATTRIBUTE 42 #define NODE_CLASS_LAYOUT 100 -/* node resize directions */ -#define NODE_RESIZE_TOP 1 -#define NODE_RESIZE_BOTTOM 2 -#define NODE_RESIZE_RIGHT 4 -#define NODE_RESIZE_LEFT 8 - typedef enum eNodeSizePreset { NODE_SIZE_DEFAULT, NODE_SIZE_SMALL, @@ -414,13 +403,12 @@ typedef struct bNodeTreeType { /* calls allowing threaded composite */ void (*localize)(struct bNodeTree *localtree, struct bNodeTree *ntree); - 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); + bool (*validate_link)(eNodeSocketDatatype from, eNodeSocketDatatype to); void (*node_add_init)(struct bNodeTree *ntree, struct bNode *bnode); @@ -443,7 +431,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(); \ @@ -457,36 +445,60 @@ struct GHashIterator *ntreeTypeGetIterator(void); } \ (void)0 +/** + * 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 type-info on registration + * and do necessary updates. + */ void ntreeSetTypes(const struct bContext *C, struct bNodeTree *ntree); struct bNodeTree *ntreeAddTree(struct Main *bmain, const char *name, const char *idname); /* copy/free funcs, need to manage ID users */ + +/** + * Free (or release) any data used by this node-tree. + * Does not free the node-tree itself and does no ID user counting. + */ void ntreeFreeTree(struct bNodeTree *ntree); -/* Free tree which is embedded into another datablock. */ +/** + * Free tree which is embedded into another data-block. + */ void ntreeFreeEmbeddedTree(struct bNodeTree *ntree); struct bNodeTree *ntreeCopyTree_ex(const struct bNodeTree *ntree, struct Main *bmain, - const bool do_id_user); + bool do_id_user); struct bNodeTree *ntreeCopyTree(struct Main *bmain, const struct bNodeTree *ntree); +/** + * Get address of potential node-tree pointer of given ID. + * + * \warning Using this function directly is potentially dangerous, if you don't know or are not + * sure, please use `ntreeFromID()` instead. + */ struct bNodeTree **BKE_ntree_ptr_from_id(struct ID *id); +/** + * Returns the private NodeTree object of the data-block, if it has one. + */ struct bNodeTree *ntreeFromID(struct ID *id); void ntreeFreeLocalNode(struct bNodeTree *ntree, struct bNode *node); void ntreeFreeLocalTree(struct bNodeTree *ntree); struct bNode *ntreeFindType(const struct bNodeTree *ntree, int type); -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, int tree_update_flag); +void ntreeUpdateAllUsers(struct Main *main, struct ID *id); void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, int *r_deplist_len); +void ntreeUpdateNodeLevels(struct bNodeTree *ntree); -/* XXX old trees handle output flags automatically based on special output +/** + * XXX: old trees handle output flags automatically based on special output * node types and last active selection. * New tree types have a per-output socket flag to indicate the final output to use explicitly. */ @@ -494,14 +506,25 @@ void ntreeSetOutput(struct bNodeTree *ntree); void ntreeFreeCache(struct bNodeTree *ntree); -bool ntreeNodeExists(const struct bNodeTree *ntree, const struct bNode *testnode); -bool ntreeOutputExists(const struct bNode *node, const struct bNodeSocket *testsock); -void ntreeNodeFlagSet(const bNodeTree *ntree, const int flag, const bool enable); +void ntreeNodeFlagSet(const bNodeTree *ntree, int flag, bool enable); +/** + * Returns localized tree for execution in threads. + */ struct bNodeTree *ntreeLocalize(struct bNodeTree *ntree); -void ntreeLocalSync(struct bNodeTree *localtree, struct bNodeTree *ntree); +/** + * Merge local tree results back, and free local tree. + * + * We have to assume the editor already changed completely. + */ void ntreeLocalMerge(struct Main *bmain, struct bNodeTree *localtree, struct bNodeTree *ntree); +/** + * This is only direct data, tree itself should have been written. + */ void ntreeBlendWrite(struct BlendWriter *writer, struct bNodeTree *ntree); +/** + * \note `ntree` itself has been read! + */ void ntreeBlendReadData(struct BlendDataReader *reader, struct bNodeTree *ntree); void ntreeBlendReadLib(struct BlendLibReader *reader, struct bNodeTree *ntree); void ntreeBlendReadExpand(struct BlendExpander *expander, struct bNodeTree *ntree); @@ -511,6 +534,7 @@ void ntreeBlendReadExpand(struct BlendExpander *expander, struct bNodeTree *ntre /* -------------------------------------------------------------------- */ /** \name Node Tree Interface * \{ */ + struct bNodeSocket *ntreeFindSocketInterface(struct bNodeTree *ntree, eNodeSocketInOut in_out, const char *identifier); @@ -545,10 +569,10 @@ void ntreeInterfaceTypeUpdate(struct bNodeTree *ntree); struct bNodeType *nodeTypeFind(const char *idname); void nodeRegisterType(struct bNodeType *ntype); void nodeUnregisterType(struct bNodeType *ntype); -bool nodeTypeUndefined(struct bNode *node); +bool nodeTypeUndefined(const 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(); \ @@ -574,7 +598,7 @@ 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(); \ @@ -598,13 +622,6 @@ struct bNodeSocket *nodeAddSocket(struct bNodeTree *ntree, const char *idname, const char *identifier, const char *name); -struct bNodeSocket *nodeInsertSocket(struct bNodeTree *ntree, - struct bNode *node, - eNodeSocketInOut in_out, - const char *idname, - struct bNodeSocket *next_sock, - const char *identifier, - const char *name); struct bNodeSocket *nodeAddStaticSocket(struct bNodeTree *ntree, struct bNode *node, eNodeSocketInOut in_out, @@ -612,14 +629,6 @@ struct bNodeSocket *nodeAddStaticSocket(struct bNodeTree *ntree, int subtype, const char *identifier, const char *name); -struct bNodeSocket *nodeInsertStaticSocket(struct bNodeTree *ntree, - struct bNode *node, - eNodeSocketInOut in_out, - int type, - int subtype, - struct bNodeSocket *next_sock, - 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, @@ -635,34 +644,47 @@ void nodeModifySocketTypeStatic( struct bNode *nodeAddNode(const struct bContext *C, struct bNodeTree *ntree, const char *idname); struct bNode *nodeAddStaticNode(const struct bContext *C, struct bNodeTree *ntree, int type); +/** + * \note Goes over entire tree. + */ void nodeUnlinkNode(struct bNodeTree *ntree, struct bNode *node); +/** + * Find the first available, non-duplicate name for a given node. + */ void nodeUniqueName(struct bNodeTree *ntree, struct bNode *node); -/* Delete node, associated animation data and ID user count. */ +/** + * Delete node, associated animation data and ID user count. + */ void nodeRemoveNode(struct Main *bmain, struct bNodeTree *ntree, struct bNode *node, bool do_id_user); -struct bNode *BKE_node_copy_ex(struct bNodeTree *ntree, - const struct bNode *node_src, - const int flag, - const bool unique_name); +#ifdef __cplusplus -/* Same as BKE_node_copy_ex() but stores pointers to a new node and its sockets in the source - * node. - * - * NOTE: DANGER ZONE! - * - * TODO(sergey): Maybe it's better to make BKE_node_copy_ex() return a mapping from old node and - * sockets to new one. */ -struct bNode *BKE_node_copy_store_new_pointers(struct bNodeTree *ntree, - struct bNode *node_src, - const int flag); -struct bNodeTree *ntreeCopyTree_ex_new_pointers(const struct bNodeTree *ntree, - struct Main *bmain, - const bool do_id_user); +namespace blender::bke { +/** + * \note keeps socket list order identical, for copying links. + * \note `unique_name` should usually be true, unless the \a dst_tree is temporary, + * or the names can already be assumed valid. + */ +bNode *node_copy_with_mapping(bNodeTree *dst_tree, + const bNode &node_src, + int flag, + bool unique_name, + Map<const bNodeSocket *, bNodeSocket *> &new_socket_map); + +bNode *node_copy(bNodeTree *dst_tree, const bNode &src_node, int flag, bool unique_name); + +} // namespace blender::bke + +#endif + +/** + * Also used via RNA API, so we check for proper input output direction. + */ struct bNodeLink *nodeAddLink(struct bNodeTree *ntree, struct bNode *fromnode, struct bNodeSocket *fromsock, @@ -686,25 +708,62 @@ void nodePositionRelative(struct bNode *from_node, struct bNodeSocket *to_sock); void nodePositionPropagate(struct bNode *node); +/** + * Finds a node based on its name. + */ struct bNode *nodeFindNodebyName(struct bNodeTree *ntree, const char *name); +/** + * Finds a node based on given socket and returns true on success. + */ bool nodeFindNode(struct bNodeTree *ntree, struct bNodeSocket *sock, struct bNode **r_node, int *r_sockindex); +/** + * \note Recursive. + */ struct bNode *nodeFindRootParent(bNode *node); +/** + * \returns true if \a child has \a parent as a parent/grandparent/... etc. + * \note Recursive + */ bool nodeIsChildOf(const bNode *parent, const bNode *child); +/** + * Iterate over a chain of nodes, starting with \a node_start, executing + * \a callback for each node (which can return false to end iterator). + * + * \param reversed: for backwards iteration + * \note Recursive + */ void nodeChainIter(const bNodeTree *ntree, const bNode *node_start, bool (*callback)(bNode *, bNode *, void *, const bool), void *userdata, - const bool reversed); + bool reversed); +/** + * Iterate over a chain of nodes, starting with \a node_start, executing + * \a callback for each node (which can return false to end iterator). + * + * Faster than nodeChainIter. Iter only once per node. + * Can be called recursively (using another nodeChainIterBackwards) by + * setting the recursion_lvl accordingly. + * + * \note Needs updated socket links (ntreeUpdateTree). + * \note Recursive + */ void nodeChainIterBackwards(const bNodeTree *ntree, const bNode *node_start, bool (*callback)(bNode *, bNode *, void *), void *userdata, int recursion_lvl); +/** + * Iterate over all parents of \a node, executing \a callback for each parent + * (which can return false to end iterator) + * + * \note Recursive + */ void nodeParentsIter(bNode *node, bool (*callback)(bNode *, void *), void *userdata); struct bNodeLink *nodeFindLink(struct bNodeTree *ntree, @@ -713,30 +772,49 @@ struct bNodeLink *nodeFindLink(struct bNodeTree *ntree, int nodeCountSocketLinks(const struct bNodeTree *ntree, const struct bNodeSocket *sock); void nodeSetSelected(struct bNode *node, bool select); +/** + * Two active flags, ID nodes have special flag for buttons display. + */ void nodeSetActive(struct bNodeTree *ntree, struct bNode *node); struct bNode *nodeGetActive(struct bNodeTree *ntree); -struct bNode *nodeGetActiveID(struct bNodeTree *ntree, short idtype); -bool nodeSetActiveID(struct bNodeTree *ntree, short idtype, struct ID *id); void nodeClearActive(struct bNodeTree *ntree); -void nodeClearActiveID(struct bNodeTree *ntree, short idtype); +/** + * Two active flags, ID nodes have special flag for buttons display. + */ struct bNode *nodeGetActiveTexture(struct bNodeTree *ntree); -void nodeUpdate(struct bNodeTree *ntree, struct bNode *node); -bool nodeUpdateID(struct bNodeTree *ntree, struct ID *id); -void nodeUpdateInternalLinks(struct bNodeTree *ntree, struct bNode *node); - int nodeSocketIsHidden(const struct bNodeSocket *sock); -void ntreeTagUsedSockets(struct bNodeTree *ntree); -void nodeSetSocketAvailability(struct bNodeSocket *sock, bool is_available); +void nodeSetSocketAvailability(struct bNodeTree *ntree, + struct bNodeSocket *sock, + bool is_available); int nodeSocketLinkLimit(const struct bNodeSocket *sock); -void nodeDeclarationEnsure(struct bNodeTree *ntree, struct bNode *node); +/** + * 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(struct bNodeTree *ntree, struct bNode *node); +/** + * 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(struct bNodeTree *ntree, struct bNode *node); +/** + * Update `socket->declaration` for all sockets in the node. This assumes that the node declaration + * and sockets are up to date already. + */ +void nodeSocketDeclarationsUpdate(struct bNode *node); -/* Node Clipboard */ +/** + * Node Clipboard. + */ void BKE_node_clipboard_init(const struct bNodeTree *ntree); void BKE_node_clipboard_clear(void); void BKE_node_clipboard_free(void); +/** + * Return false when one or more ID's are lost. + */ bool BKE_node_clipboard_validate(void); void BKE_node_clipboard_add_node(struct bNode *node); void BKE_node_clipboard_add_link(struct bNodeLink *link); @@ -744,13 +822,19 @@ const struct ListBase *BKE_node_clipboard_get_nodes(void); const struct ListBase *BKE_node_clipboard_get_links(void); int BKE_node_clipboard_get_type(void); -/* Node Instance Hash */ +/** + * 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); +/** + * Magic number for initial hash key. + */ extern const bNodeInstanceKey NODE_INSTANCE_KEY_BASE; extern const bNodeInstanceKey NODE_INSTANCE_KEY_NONE; @@ -821,40 +905,39 @@ bNodePreview *BKE_node_preview_verify( struct bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create); bNodePreview *BKE_node_preview_copy(struct bNodePreview *preview); void BKE_node_preview_free(struct bNodePreview *preview); -void BKE_node_preview_init_tree(struct bNodeTree *ntree, - int xsize, - int ysize, - bool create_previews); -void BKE_node_preview_free_tree(struct bNodeTree *ntree); +void BKE_node_preview_init_tree(struct bNodeTree *ntree, int xsize, int ysize); void BKE_node_preview_remove_unused(struct bNodeTree *ntree); void BKE_node_preview_clear(struct bNodePreview *preview); void BKE_node_preview_clear_tree(struct bNodeTree *ntree); -void BKE_node_preview_sync_tree(struct bNodeTree *to_ntree, struct bNodeTree *from_ntree); void BKE_node_preview_merge_tree(struct bNodeTree *to_ntree, struct bNodeTree *from_ntree, bool remove_old); -void BKE_node_preview_set_pixel( - struct bNodePreview *preview, const float col[4], int x, int y, bool do_manage); - /** \} */ /* -------------------------------------------------------------------- */ /** \name Node Type Access * \{ */ -void nodeLabel(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen); +void nodeLabel(const struct bNodeTree *ntree, const struct bNode *node, char *label, int maxlen); +/** + * Get node socket label if it is set. + */ const char *nodeSocketLabel(const struct bNodeSocket *sock); bool nodeGroupPoll(struct bNodeTree *nodetree, struct bNodeTree *grouptree, const char **r_disabled_hint); -/* Init a new node type struct with default values and callbacks */ -void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass, short flag); -void node_type_base_custom( - struct bNodeType *ntype, const char *idname, const char *name, short nclass, short flag); +/** + * Initialize a new node type struct with default values and callbacks. + */ +void node_type_base(struct bNodeType *ntype, int type, const char *name, short nclass); +void node_type_base_custom(struct bNodeType *ntype, + const char *idname, + const char *name, + short nclass); void node_type_socket_templates(struct bNodeType *ntype, struct bNodeSocketTemplate *inputs, struct bNodeSocketTemplate *outputs); @@ -862,15 +945,16 @@ void node_type_size(struct bNodeType *ntype, int width, int minwidth, int maxwid void node_type_size_preset(struct bNodeType *ntype, eNodeSizePreset size); void node_type_init(struct bNodeType *ntype, void (*initfunc)(struct bNodeTree *ntree, struct bNode *node)); +/** + * \warning Nodes defining a storage type _must_ allocate this for new nodes. + * Otherwise nodes will reload as undefined (T46619). + */ void node_type_storage(struct bNodeType *ntype, const char *storagename, void (*freefunc)(struct bNode *node), void (*copyfunc)(struct bNodeTree *dest_ntree, struct bNode *dest_node, const struct bNode *src_node)); -void node_type_label( - struct bNodeType *ntype, - void (*labelfunc)(struct bNodeTree *ntree, struct bNode *, char *label, int maxlen)); void node_type_update(struct bNodeType *ntype, void (*updatefunc)(struct bNodeTree *ntree, struct bNode *node)); void node_type_group_update(struct bNodeType *ntype, @@ -882,8 +966,6 @@ void node_type_exec(struct bNodeType *ntype, NodeFreeExecFunction free_exec_fn, NodeExecFunction exec_fn); void node_type_gpu(struct bNodeType *ntype, NodeGPUExecFunction gpu_fn); -void node_type_internal_links(struct bNodeType *ntype, - void (*update_internal_links)(struct bNodeTree *, struct bNode *)); /** \} */ @@ -978,26 +1060,20 @@ bool BKE_node_tree_iter_step(struct NodeTreeIterStore *ntreeiter, } \ } \ ((void)0) + /** \} */ /* -------------------------------------------------------------------- */ /** \name Node Tree */ -void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, - struct Scene *scene, - const int layer_index); +void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, int layer_index); /* -------------------------------------------------------------------- */ /** \name Shader Nodes * \{ */ /* 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. */ - -//#define SH_NODE_OUTPUT 1 //#define SH_NODE_MATERIAL 100 #define SH_NODE_RGB 101 @@ -1021,7 +1097,6 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, #define SH_NODE_SEPRGB 120 #define SH_NODE_COMBRGB 121 #define SH_NODE_HUE_SAT 122 -#define NODE_DYNAMIC 123 #define SH_NODE_OUTPUT_MATERIAL 124 #define SH_NODE_OUTPUT_WORLD 125 @@ -1102,22 +1177,8 @@ 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 - -/* custom defines options for Material node */ -// #define SH_NODE_MAT_DIFF 1 -// #define SH_NODE_MAT_SPEC 2 -// #define SH_NODE_MAT_NEG 4 - -/* API */ - -struct bNodeTreeExec *ntreeShaderBeginExecTree(struct bNodeTree *ntree); -void ntreeShaderEndExecTree(struct bNodeTreeExec *exec); -struct bNode *ntreeShaderOutputNode(struct bNodeTree *ntree, int target); - -void ntreeGPUMaterialNodes(struct bNodeTree *localtree, - struct GPUMaterial *mat, - bool *has_surface_output, - bool *has_volume_output); +#define SH_NODE_CURVE_FLOAT 709 +#define SH_NODE_POINT_INFO 710 /** \} */ @@ -1128,36 +1189,6 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, /* output socket defines */ #define RRES_OUT_IMAGE 0 #define RRES_OUT_ALPHA 1 -// #define RRES_OUT_Z 2 -// #define RRES_OUT_NORMAL 3 -// #define RRES_OUT_UV 4 -// #define RRES_OUT_VEC 5 -// #define RRES_OUT_RGBA 6 -#define RRES_OUT_DIFF 7 -// #define RRES_OUT_SPEC 8 -// #define RRES_OUT_SHADOW 9 -// #define RRES_OUT_AO 10 -// #define RRES_OUT_REFLECT 11 -// #define RRES_OUT_REFRACT 12 -// #define RRES_OUT_INDIRECT 13 -// #define RRES_OUT_INDEXOB 14 -// #define RRES_OUT_INDEXMA 15 -// #define RRES_OUT_MIST 16 -// #define RRES_OUT_EMIT 17 -// #define RRES_OUT_ENV 18 -// #define RRES_OUT_DIFF_DIRECT 19 -// #define RRES_OUT_DIFF_INDIRECT 20 -// #define RRES_OUT_DIFF_COLOR 21 -// #define RRES_OUT_GLOSSY_DIRECT 22 -// #define RRES_OUT_GLOSSY_INDIRECT 23 -// #define RRES_OUT_GLOSSY_COLOR 24 -// #define RRES_OUT_TRANSM_DIRECT 25 -// #define RRES_OUT_TRANSM_INDIRECT 26 -// #define RRES_OUT_TRANSM_COLOR 27 -// #define RRES_OUT_SUBSURFACE_DIRECT 28 -// #define RRES_OUT_SUBSURFACE_INDIRECT 29 -// #define RRES_OUT_SUBSURFACE_COLOR 30 -// #define RRES_OUT_DEBUG 31 /* NOTE: types are needed to restore callbacks, don't change values. */ #define CMP_NODE_VIEWER 201 @@ -1258,6 +1289,8 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, #define CMP_NODE_EXPOSURE 325 #define CMP_NODE_CRYPTOMATTE 326 #define CMP_NODE_POSTERIZE 327 +#define CMP_NODE_CONVERT_COLOR_SPACE 328 +#define CMP_NODE_SCENE_TIME 329 /* channel toggles */ #define CMP_CHAN_RGB 1 @@ -1296,61 +1329,6 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, #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, - struct RenderData *rd, - int rendering, - int do_previews, - const struct ColorManagedViewSettings *view_settings, - const struct ColorManagedDisplaySettings *display_settings, - const char *view_name); -void ntreeCompositTagRender(struct Scene *scene); -void ntreeCompositUpdateRLayers(struct bNodeTree *ntree); -void ntreeCompositRegisterPass(struct bNodeTree *ntree, - struct Scene *scene, - struct ViewLayer *view_layer, - const char *name, - eNodeSocketDatatype type); -void ntreeCompositClearTags(struct bNodeTree *ntree); - -struct bNodeSocket *ntreeCompositOutputFileAddSocket(struct bNodeTree *ntree, - struct bNode *node, - const char *name, - struct ImageFormatData *im_format); -int ntreeCompositOutputFileRemoveActiveSocket(struct bNodeTree *ntree, struct bNode *node); -void ntreeCompositOutputFileSetPath(struct bNode *node, - struct bNodeSocket *sock, - const char *name); -void ntreeCompositOutputFileSetLayer(struct bNode *node, - struct bNodeSocket *sock, - const char *name); -/* needed in do_versions */ -void ntreeCompositOutputFileUniquePath(struct ListBase *list, - struct bNodeSocket *sock, - const char defname[], - char delim); -void ntreeCompositOutputFileUniqueLayer(struct ListBase *list, - struct bNodeSocket *sock, - const char defname[], - char delim); - -void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *ntree, bNode *node); -void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *ntree, bNode *node); - -void ntreeCompositCryptomatteSyncFromAdd(const Scene *scene, bNode *node); -void ntreeCompositCryptomatteSyncFromRemove(bNode *node); -bNodeSocket *ntreeCompositCryptomatteAddSocket(bNodeTree *ntree, bNode *node); -int ntreeCompositCryptomatteRemoveSocket(bNodeTree *ntree, bNode *node); -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 - * render layer or image. */ -void ntreeCompositCryptomatteUpdateLayerNames(const Scene *scene, bNode *node); -struct CryptomatteSession *ntreeCompositCryptomatteSession(const Scene *scene, bNode *node); - /** \} */ /* -------------------------------------------------------------------- */ @@ -1387,23 +1365,6 @@ struct TexResult; #define TEX_NODE_PROC 500 #define TEX_NODE_PROC_MAX 600 -/* API */ -void ntreeTexCheckCyclics(struct bNodeTree *ntree); - -struct bNodeTreeExec *ntreeTexBeginExecTree(struct bNodeTree *ntree); -void ntreeTexEndExecTree(struct bNodeTreeExec *exec); -int ntreeTexExecTree(struct bNodeTree *ntree, - struct TexResult *target, - const float co[3], - float dxt[3], - float dyt[3], - int osatex, - const short thread, - const struct Tex *tex, - short which_output, - int cfra, - int preview, - struct MTex *mtex); /** \} */ /* -------------------------------------------------------------------- */ @@ -1413,7 +1374,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_TRIANGULATE 1000 #define GEO_NODE_LEGACY_EDGE_SPLIT 1001 #define GEO_NODE_TRANSFORM 1002 -#define GEO_NODE_BOOLEAN 1003 +#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 @@ -1436,10 +1397,10 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_COLLECTION_INFO 1023 #define GEO_NODE_IS_VIEWPORT 1024 #define GEO_NODE_LEGACY_ATTRIBUTE_PROXIMITY 1025 -#define GEO_NODE_VOLUME_TO_MESH 1026 +#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_MESH_SUBDIVIDE 1029 +#define GEO_NODE_SUBDIVIDE_MESH 1029 #define GEO_NODE_ATTRIBUTE_REMOVE 1030 #define GEO_NODE_LEGACY_ATTRIBUTE_CONVERT 1031 #define GEO_NODE_MESH_PRIMITIVE_CUBE 1032 @@ -1457,11 +1418,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_LEGACY_ATTRIBUTE_TRANSFER 1044 #define GEO_NODE_CURVE_TO_MESH 1045 #define GEO_NODE_LEGACY_ATTRIBUTE_CURVE_MAP 1046 -#define GEO_NODE_CURVE_RESAMPLE 1047 +#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_REPLACE_MATERIAL 1051 #define GEO_NODE_LEGACY_MESH_TO_CURVE 1052 #define GEO_NODE_LEGACY_DELETE_GEOMETRY 1053 #define GEO_NODE_CURVE_LENGTH 1054 @@ -1481,32 +1442,88 @@ int ntreeTexExecTree(struct bNodeTree *ntree, #define GEO_NODE_CURVE_PRIMITIVE_LINE 1068 #define GEO_NODE_LEGACY_CURVE_ENDPOINTS 1069 #define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070 -#define GEO_NODE_CURVE_TRIM 1071 +#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_CURVE_FILL 1075 +#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_ATTRIBUTE_CAPTURE 1080 +#define GEO_NODE_CAPTURE_ATTRIBUTE 1080 #define GEO_NODE_MATERIAL_SELECTION 1081 -#define GEO_NODE_MATERIAL_ASSIGN 1082 +#define GEO_NODE_SET_MATERIAL 1082 #define GEO_NODE_REALIZE_INSTANCES 1083 #define GEO_NODE_ATTRIBUTE_STATISTIC 1084 -#define GEO_NODE_CURVE_SAMPLE 1085 +#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_CURVE_FILLET 1089 +#define GEO_NODE_CURVE_SPLINE_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_CURVE_REVERSE 1095 +#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 +#define GEO_NODE_ATTRIBUTE_DOMAIN_SIZE 1136 +#define GEO_NODE_DUAL_MESH 1137 +#define GEO_NODE_INPUT_MESH_EDGE_VERTICES 1138 +#define GEO_NODE_INPUT_MESH_FACE_AREA 1139 +#define GEO_NODE_INPUT_MESH_FACE_NEIGHBORS 1140 +#define GEO_NODE_INPUT_MESH_VERTEX_NEIGHBORS 1141 +#define GEO_NODE_GEOMETRY_TO_INSTANCE 1142 +#define GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS 1143 +#define GEO_NODE_INPUT_MESH_ISLAND 1144 +#define GEO_NODE_INPUT_SCENE_TIME 1145 +#define GEO_NODE_ACCUMULATE_FIELD 1146 +#define GEO_NODE_INPUT_MESH_EDGE_ANGLE 1147 +#define GEO_NODE_FIELD_AT_INDEX 1148 +#define GEO_NODE_CURVE_PRIMITIVE_ARC 1149 +#define GEO_NODE_FLIP_FACES 1150 +#define GEO_NODE_SCALE_ELEMENTS 1151 +#define GEO_NODE_EXTRUDE_MESH 1152 +#define GEO_NODE_MERGE_BY_DISTANCE 1153 /** \} */ @@ -1515,34 +1532,53 @@ int ntreeTexExecTree(struct bNodeTree *ntree, * \{ */ #define FN_NODE_BOOLEAN_MATH 1200 -#define FN_NODE_FLOAT_COMPARE 1202 +#define FN_NODE_COMPARE 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_STRING_SUBSTRING 1212 +#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 /** \} */ void BKE_node_system_init(void); void BKE_node_system_exit(void); -/* -------------------------------------------------------------------- */ -/* evaluation support, */ - -struct Depsgraph; - -void BKE_nodetree_shading_params_eval(struct Depsgraph *depsgraph, - struct bNodeTree *ntree_dst, - const struct bNodeTree *ntree_src); - extern struct bNodeType NodeTypeUndefined; extern struct bNodeSocketType NodeSocketTypeUndefined; #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +namespace blender::bke { + +bNodeSocket *node_find_enabled_socket(bNode &node, eNodeSocketInOut in_out, StringRef name); +bNodeSocket *node_find_enabled_input_socket(bNode &node, StringRef name); +bNodeSocket *node_find_enabled_output_socket(bNode &node, StringRef name); + +} // namespace blender::bke + +#endif + +#define NODE_STORAGE_FUNCS(StorageT) \ + [[maybe_unused]] static StorageT &node_storage(bNode &node) \ + { \ + return *static_cast<StorageT *>(node.storage); \ + } \ + [[maybe_unused]] static const StorageT &node_storage(const bNode &node) \ + { \ + return *static_cast<const StorageT *>(node.storage); \ + } diff --git a/source/blender/blenkernel/BKE_node_tree_update.h b/source/blender/blenkernel/BKE_node_tree_update.h new file mode 100644 index 00000000000..443ceafb073 --- /dev/null +++ b/source/blender/blenkernel/BKE_node_tree_update.h @@ -0,0 +1,110 @@ +/* + * 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 + */ + +struct ID; +struct Main; +struct bNode; +struct bNodeLink; +struct bNodeSocket; +struct bNodeTree; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Tag tree as changed without providing any more information about what has changed exactly. + * The update process has to assume that everything may have changed. + * + * Using one of the methods below to tag the tree after changes is preferred when possible. + */ +void BKE_ntree_update_tag_all(struct bNodeTree *ntree); + +/** + * More specialized tag functions that may result in a more efficient update. + */ + +void BKE_ntree_update_tag_node_property(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_new(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_node_mute(struct bNodeTree *ntree, struct bNode *node); +void BKE_ntree_update_tag_node_internal_link(struct bNodeTree *ntree, struct bNode *node); + +void BKE_ntree_update_tag_socket_property(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_new(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_type(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_availability(struct bNodeTree *ntree, struct bNodeSocket *socket); +void BKE_ntree_update_tag_socket_removed(struct bNodeTree *ntree); + +void BKE_ntree_update_tag_link_changed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_removed(struct bNodeTree *ntree); +void BKE_ntree_update_tag_link_added(struct bNodeTree *ntree, struct bNodeLink *link); +void BKE_ntree_update_tag_link_mute(struct bNodeTree *ntree, struct bNodeLink *link); + +/** Used after file loading when run-time data on the tree has not been initialized yet. */ +void BKE_ntree_update_tag_missing_runtime_data(struct bNodeTree *ntree); +/** Used when the interface sockets/values have changed. */ +void BKE_ntree_update_tag_interface(struct bNodeTree *ntree); +/** Used when an id data block changed that might be used by nodes that need to be updated. */ +void BKE_ntree_update_tag_id_changed(struct Main *bmain, struct ID *id); + +typedef struct NodeTreeUpdateExtraParams { + /** + * Data passed into the callbacks. + */ + void *user_data; + + /** + * Called for every tree that has been changed during the update. This can be used to send + * notifiers to trigger redraws or depsgraph updates. + */ + void (*tree_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); + + /** + * Called for every tree whose output value may have changed based on the provided update tags. + * This can be used to tag the depsgraph if necessary. + */ + void (*tree_output_changed_fn)(struct ID *, struct bNodeTree *, void *user_data); +} NodeTreeUpdateExtraParams; + +/** + * Updates #bmain based on changes to node trees. + */ +void BKE_ntree_update_main(struct Main *bmain, struct NodeTreeUpdateExtraParams *params); + +/** + * Same as #BKE_ntree_update_main, but will first only look at the provided tree and only looks + * at #bmain when something relevant for other data-blocks changed. This avoids scanning #bmain in + * many cases. + * + * If #bmain is null, only the provided tree is updated. This should only be used in very rare + * cases because it may result it incorrectly synced data in DNA. + * + * If #tree is null, this is the same as calling #BKE_ntree_update_main. + */ +void BKE_ntree_update_main_tree(struct Main *bmain, + struct bNodeTree *ntree, + struct NodeTreeUpdateExtraParams *params); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_object.h b/source/blender/blenkernel/BKE_object.h index 0e153c5a82a..99758f4ad78 100644 --- a/source/blender/blenkernel/BKE_object.h +++ b/source/blender/blenkernel/BKE_object.h @@ -48,25 +48,35 @@ struct RegionView3D; struct RigidBodyWorld; struct Scene; struct ShaderFxData; +struct SubsurfModifierData; struct View3D; struct ViewLayer; void BKE_object_workob_clear(struct Object *workob); +/** + * For calculation of the inverse parent transform, only used for editor. + * + * It assumes the object parent is already in the depsgraph. + * Otherwise, after changing ob->parent you need to call: + * - #DEG_relations_tag_update(bmain); + * - #BKE_scene_graph_update_tagged(depsgraph, bmain); + */ void BKE_object_workob_calc_parent(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct Object *workob); void BKE_object_transform_copy(struct Object *ob_tar, const struct Object *ob_src); -void BKE_object_copy_softbody(struct Object *ob_dst, const struct Object *ob_src, const int flag); -struct ParticleSystem *BKE_object_copy_particlesystem(struct ParticleSystem *psys, const int flag); -void BKE_object_copy_particlesystems(struct Object *ob_dst, - const struct Object *ob_src, - const int flag); +void BKE_object_copy_softbody(struct Object *ob_dst, const struct Object *ob_src, int flag); +struct ParticleSystem *BKE_object_copy_particlesystem(struct ParticleSystem *psys, int flag); +void BKE_object_copy_particlesystems(struct Object *ob_dst, const struct Object *ob_src, int flag); void BKE_object_free_particlesystems(struct Object *ob); void BKE_object_free_softbody(struct Object *ob); void BKE_object_free_curve_cache(struct Object *ob); +/** + * Free data derived from mesh, called when mesh changes or is freed. + */ void BKE_object_free_derived_caches(struct Object *ob); void BKE_object_free_caches(struct Object *object); @@ -75,29 +85,71 @@ 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); +/** + * \return True if the object's type supports regular modifiers (not grease pencil modifiers). + */ bool BKE_object_supports_modifiers(const struct Object *ob); bool BKE_object_support_modifier_type_check(const struct Object *ob, int modifier_type); /* Active modifier. */ + +/** + * Set the object's active modifier. + * + * \param md: If nullptr, only clear the active modifier, otherwise + * it must be in the #Object.modifiers list. + */ void BKE_object_modifier_set_active(struct Object *ob, struct ModifierData *md); struct ModifierData *BKE_object_active_modifier(const struct Object *ob); +/** + * Copy a single modifier. + * + * \note *Do not* use this function to copy a whole modifier stack (see note below too). Use + * `BKE_object_modifier_stack_copy` instead. + * + * \note Complex modifiers relaying on other data (like e.g. dynamic paint or fluid using particle + * systems) are not always 100% 'correctly' copied here, since we have to use heuristics to decide + * which particle system to use or add in `ob_dst`, and it's placement in the stack, etc. If used + * more than once, this function should preferably be called in stack order. + */ bool BKE_object_copy_modifier(struct Main *bmain, struct Scene *scene, 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); +/** + * Copy a single GPencil modifier. + * + * \note *Do not* use this function to copy a whole modifier stack. Use + * `BKE_object_modifier_stack_copy` instead. + */ +bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, struct GpencilModifierData *gmd_src); +/** + * Copy the whole stack of modifiers from one object into another. + * + * \warning *Does not* clear modifier stack and related data (particle systems, soft-body, + * etc.) in `ob_dst`, if needed calling code must do it. + * + * \param do_copy_all: If true, even modifiers that should not support copying (like Hook one) + * will be duplicated. + */ bool BKE_object_modifier_stack_copy(struct Object *ob_dst, const struct Object *ob_src, - const bool do_copy_all, - const int flag_subdata); + bool do_copy_all, + int flag_subdata); void BKE_object_link_modifiers(struct Object *ob_dst, const struct Object *ob_src); -void BKE_object_free_modifiers(struct Object *ob, const int flag); -void BKE_object_free_shaderfx(struct Object *ob, const int flag); - +void BKE_object_free_modifiers(struct Object *ob, int flag); +void BKE_object_free_shaderfx(struct Object *ob, int flag); + +/** + * Proxy rule: + * - `lib_object->proxy_from` == the one we borrow from, set temporally while object_update. + * - `local_object->proxy` == pointer to library object, saved in files and read. + * - `local_object->proxy_group` == pointer to collection dupli-object, saved in files and read. + */ void BKE_object_make_proxy(struct Main *bmain, struct Object *ob, struct Object *target, @@ -105,6 +157,9 @@ void BKE_object_make_proxy(struct Main *bmain, void BKE_object_copy_proxy_drivers(struct Object *ob, struct Object *target); bool BKE_object_exists_check(struct Main *bmain, const struct Object *obtest); +/** + * Actual check for internal data, not context or flags. + */ bool BKE_object_is_in_editmode(const struct Object *ob); bool BKE_object_is_in_editmode_vgroup(const struct Object *ob); bool BKE_object_is_in_wpaint_select_vert(const struct Object *ob); @@ -115,6 +170,9 @@ bool BKE_object_data_is_in_editmode(const struct ID *id); char *BKE_object_data_editmode_flush_ptr_get(struct ID *id); +/** + * Updates select_id of all objects in the given \a bmain. + */ void BKE_object_update_select_id(struct Main *bmain); typedef enum eObjectVisibilityResult { @@ -124,14 +182,33 @@ typedef enum eObjectVisibilityResult { OB_VISIBLE_ALL = (OB_VISIBLE_SELF | OB_VISIBLE_PARTICLES | OB_VISIBLE_INSTANCES), } eObjectVisibilityResult; -int BKE_object_visibility(const struct Object *ob, const int dag_eval_mode); +/** + * Return which parts of the object are visible, as evaluated by depsgraph. + */ +int BKE_object_visibility(const struct Object *ob, int dag_eval_mode); +/** + * More general add: creates minimum required data, but without vertices etc. + */ struct Object *BKE_object_add_only_object(struct Main *bmain, int type, const char *name) ATTR_NONNULL(1) ATTR_RETURNS_NONNULL; +/** + * General add: to scene, with layer from area and default name. + * + * Object is added to the active #Collection. + * If there is no linked collection to the active #ViewLayer we create a new one. + * + * \note Creates minimum required data, but without vertices etc. + */ struct Object *BKE_object_add(struct Main *bmain, struct ViewLayer *view_layer, int type, const char *name) ATTR_NONNULL(1, 2) ATTR_RETURNS_NONNULL; +/** + * Add a new object, using another one as a reference + * + * \param ob_src: object to use to determine the collections of the new object. + */ struct Object *BKE_object_add_from(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer, @@ -139,6 +216,15 @@ struct Object *BKE_object_add_from(struct Main *bmain, const char *name, struct Object *ob_src) ATTR_NONNULL(1, 2, 3, 6) ATTR_RETURNS_NONNULL; +/** + * Add a new object, but assign the given data-block as the `ob->data` + * for the newly created 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. + */ struct Object *BKE_object_add_for_data(struct Main *bmain, struct ViewLayer *view_layer, int type, @@ -147,32 +233,66 @@ struct Object *BKE_object_add_for_data(struct Main *bmain, bool do_id_user) ATTR_RETURNS_NONNULL; void *BKE_object_obdata_add_from_type(struct Main *bmain, int type, const char *name) ATTR_NONNULL(1); +/** + * Return -1 on failure. + */ int BKE_object_obdata_to_type(const struct ID *id) ATTR_NONNULL(1); +/** + * Returns true if the Object is from an external blend file (libdata). + */ bool BKE_object_is_libdata(const struct Object *ob); +/** + * Returns true if the Object data is from an external blend file (libdata). + */ bool BKE_object_obdata_is_libdata(const struct Object *ob); +/** + * Perform deep-copy of object and its 'children' data-blocks (obdata, materials, actions, etc.). + * + * \param dupflag: Controls which sub-data are also duplicated + * (see #eDupli_ID_Flags in DNA_userdef_types.h). + * + * \note This function does not do any remapping to new IDs, caller must do it + * (\a #BKE_libblock_relink_to_newid()). + * \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()). + */ 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); +/** + * Use with newly created objects to set their size (used to apply scene-scale). + */ +void BKE_object_obdata_size_init(struct Object *ob, float size); void BKE_object_scale_to_mat3(struct Object *ob, float r_mat[3][3]); void BKE_object_rot_to_mat3(const struct Object *ob, float r_mat[3][3], bool use_drot); void BKE_object_mat3_to_rot(struct Object *ob, float r_mat[3][3], bool use_compat); void BKE_object_to_mat3(struct Object *ob, float r_mat[3][3]); void BKE_object_to_mat4(struct Object *ob, float r_mat[4][4]); -void BKE_object_apply_mat4(struct Object *ob, - const float mat[4][4], - const bool use_compat, - const bool use_parent); +/** + * Applies the global transformation \a mat to the \a ob using a relative parent space if + * supplied. + * + * \param mat: the global transformation mat that the object should be set object to. + * \param parent: the parent space in which this object will be set relative to + * (should probably always be parent_eval). + * \param use_compat: true to ensure that rotations are set using the + * min difference between the old and new orientation. + */ void BKE_object_apply_mat4_ex(struct Object *ob, const float mat[4][4], struct Object *parent, const float parentinv[4][4], - const bool use_compat); + bool use_compat); +/** See #BKE_object_apply_mat4_ex */ +void BKE_object_apply_mat4(struct Object *ob, + const float mat[4][4], + bool use_compat, + bool use_parent); void BKE_object_matrix_local_get(struct Object *ob, float r_mat[4][4]); bool BKE_object_pose_context_check(const struct Object *ob); @@ -181,6 +301,9 @@ struct Object *BKE_object_pose_armature_get_visible(struct Object *ob, struct ViewLayer *view_layer, struct View3D *v3d); +/** + * Access pose array with special check to get pose object when in weight paint mode. + */ struct Object **BKE_object_pose_array_get_ex(struct ViewLayer *view_layer, struct View3D *v3d, unsigned int *r_objects_len, @@ -205,7 +328,9 @@ struct Base **BKE_object_pose_base_array_get(struct ViewLayer *view_layer, void BKE_object_get_parent_matrix(struct Object *ob, struct Object *par, float r_parentmat[4][4]); -/* Compute object world transform and store it in ob->obmat. */ +/** + * Compute object world transform and store it in `ob->obmat`. + */ void BKE_object_where_is_calc(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); void BKE_object_where_is_calc_ex(struct Depsgraph *depsgraph, struct Scene *scene, @@ -216,9 +341,16 @@ void BKE_object_where_is_calc_time(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, float ctime); +/** + * Calculate object transformation matrix without recalculating dependencies and + * constraints -- assume dependencies are already solved by depsgraph. + * No changes to object and its parent would be done. + * Used for bundles orientation in 3d space relative to parented blender camera. + */ void BKE_object_where_is_calc_mat4(struct Object *ob, float r_obmat[4][4]); /* Possibly belong in own module? */ + struct BoundBox *BKE_boundbox_alloc_unit(void); void BKE_boundbox_init_from_minmax(struct BoundBox *bb, const float min[3], const float max[3]); void BKE_boundbox_calc_center_aabb(const struct BoundBox *bb, float r_cent[3]); @@ -230,6 +362,14 @@ void BKE_boundbox_minmax(const struct BoundBox *bb, struct BoundBox *BKE_object_boundbox_get(struct Object *ob); void BKE_object_dimensions_get(struct Object *ob, float r_vec[3]); +/** + * The original scale and object matrix can be passed in so any difference + * of the objects matrix and the final matrix can be accounted for, + * typically this caused by parenting, constraints or delta-scale. + * + * Re-using these values from the object causes a feedback loop + * when multiple values are modified at once in some situations. see: T69536. + */ void BKE_object_dimensions_set_ex(struct Object *ob, const float value[3], int axis_mask, @@ -237,18 +377,24 @@ void BKE_object_dimensions_set_ex(struct Object *ob, const float ob_obmat_orig[4][4]); void BKE_object_dimensions_set(struct Object *ob, const float value[3], int axis_mask); -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_empty_draw_type_set(struct Object *ob, int value); +/** + * Use this to temporally disable/enable bound-box. + */ +void BKE_object_boundbox_flag(struct Object *ob, int flag, bool set); 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_boundbox_calc_from_evaluated_geometry(struct Object *ob); +void BKE_object_minmax(struct Object *ob, float r_min[3], float r_max[3], bool use_hidden); bool BKE_object_minmax_dupli(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, float r_min[3], float r_max[3], - const bool use_hidden); + bool use_hidden); -/* sometimes min-max isn't enough, we need to loop over each point */ +/** + * Sometimes min-max isn't enough, we need to loop over each point. + */ void BKE_object_foreach_display_point(struct Object *ob, const float obmat[4][4], void (*func_cb)(const float[3], void *), @@ -275,13 +421,22 @@ void BKE_object_tfm_protected_backup(const struct Object *ob, ObjectTfmProtected void BKE_object_tfm_protected_restore(struct Object *ob, const ObjectTfmProtectedChannels *obtfm, - const short protectflag); + short protectflag); void BKE_object_tfm_copy(struct Object *object_dst, const struct Object *object_src); +/** + * Restore the object->data to a non-modifier evaluated state. + * + * Some changes done directly in evaluated object require them to be reset + * before being re-evaluated. + * For example, we need to call this before #BKE_mesh_new_from_object(), + * in case we removed/added modifiers in the evaluated object. + */ void BKE_object_eval_reset(struct Object *ob_eval); /* Dependency graph evaluation callbacks. */ + void BKE_object_eval_local_transform(struct Depsgraph *depsgraph, struct Object *ob); void BKE_object_eval_parent(struct Depsgraph *depsgraph, struct Object *ob); void BKE_object_eval_constraints(struct Depsgraph *depsgraph, @@ -294,6 +449,9 @@ void BKE_object_eval_uber_transform(struct Depsgraph *depsgraph, struct Object * void BKE_object_eval_uber_data(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * Assign #Object.data after modifier stack evaluation. + */ void BKE_object_eval_assign_data(struct Object *object, struct ID *data, bool is_owned); void BKE_object_sync_to_original(struct Depsgraph *depsgraph, struct Object *object); @@ -311,35 +469,74 @@ void BKE_object_select_update(struct Depsgraph *depsgraph, struct Object *object void BKE_object_eval_eval_base_flags(struct Depsgraph *depsgraph, struct Scene *scene, - const int view_layer_index, + int view_layer_index, struct Object *object, int base_index, - const bool is_from_set); + bool is_from_set); void BKE_object_handle_data_update(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * \warning "scene" here may not be the scene object actually resides in. + * When dealing with background-sets, "scene" is actually the active scene. + * e.g. "scene" <-- set 1 <-- set 2 ("ob" lives here) <-- set 3 <-- ... <-- set n + * rigid bodies depend on their world so use #BKE_object_handle_update_ex() + * to also pass along the current rigid body world. + */ void BKE_object_handle_update(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** + * Proxy rule: + * - lib_object->proxy_from == the one we borrow from, only set temporal and cleared here. + * - local_object->proxy == pointer to library object, saved in files and read. + * + * Function below is polluted with proxy exceptions, cleanup will follow! + * + * The main object update call, for object matrix, constraints, keys and #DispList (modifiers) + * requires flags to be set! + * + * Ideally we shouldn't have to pass the rigid body world, + * but need bigger restructuring to avoid id. + */ void BKE_object_handle_update_ex(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct RigidBodyWorld *rbw, - const bool do_proxy_update); + bool do_proxy_update); void BKE_object_sculpt_data_create(struct Object *ob); bool BKE_object_obdata_texspace_get(struct Object *ob, - short **r_texflag, + char **r_texflag, float **r_loc, float **r_size); +struct Mesh *BKE_object_get_evaluated_mesh_no_subsurf(const struct Object *object); +/** Get evaluated mesh for given object. */ struct Mesh *BKE_object_get_evaluated_mesh(const struct Object *object); +/** + * Get mesh which is not affected by modifiers: + * - For original objects it will be same as `object->data`, and it is a mesh + * which is in the corresponding #Main. + * - For copied-on-write objects it will give pointer to a copied-on-write + * mesh which corresponds to original object's mesh. + */ struct Mesh *BKE_object_get_pre_modified_mesh(const struct Object *object); +/** + * Get a mesh which corresponds to the very original mesh from #Main. + * - For original objects it will be object->data. + * - For evaluated objects it will be same mesh as corresponding original + * object uses as data. + */ struct Mesh *BKE_object_get_original_mesh(const struct Object *object); +struct Mesh *BKE_object_get_editmesh_eval_final(const struct Object *object); +struct Mesh *BKE_object_get_editmesh_eval_cage(const struct Object *object); + /* Lattice accessors. * These functions return either the regular lattice, or the edit-mode lattice, * whichever is currently in use. */ + struct Lattice *BKE_object_get_lattice(const struct Object *object); struct Lattice *BKE_object_get_evaluated_lattice(const struct Object *object); @@ -348,7 +545,7 @@ void BKE_object_delete_ptcache(struct Object *ob, int index); struct KeyBlock *BKE_object_shapekey_insert(struct Main *bmain, struct Object *ob, const char *name, - const bool from_mix); + bool from_mix); bool BKE_object_shapekey_remove(struct Main *bmain, struct Object *ob, struct KeyBlock *kb); bool BKE_object_shapekey_free(struct Main *bmain, struct Object *ob); @@ -356,12 +553,38 @@ bool BKE_object_flag_test_recursive(const struct Object *ob, short flag); bool BKE_object_is_child_recursive(const struct Object *ob_parent, const struct Object *ob_child); -/* return ModifierMode flag */ +/** + * Most important if this is modified it should _always_ return true, in certain + * cases false positives are hard to avoid (shape keys for example). + * + * \return #ModifierMode flag. + */ int BKE_object_is_modified(struct Scene *scene, struct Object *ob); +/** + * Test if object is affected by deforming modifiers (for motion blur). again + * most important is to avoid false positives, this is to skip computations + * and we can still if there was actual deformation afterwards. + */ int BKE_object_is_deform_modified(struct Scene *scene, struct Object *ob); +/** + * Check of objects moves in time. + * + * \note This function is currently optimized for usage in combination + * with modifier deformation checks (#eModifierTypeType_OnlyDeform), + * so modifiers can quickly check if their target objects moves + * (causing deformation motion blur) or not. + * + * This makes it possible to give some degree of false-positives here, + * but it's currently an acceptable tradeoff between complexity and check + * speed. In combination with checks of modifier stack and real life usage + * percentage of false-positives shouldn't be that high. + * + * \note This function does not consider physics systems. + */ bool BKE_object_moves_in_time(const struct Object *object, bool recurse_parent); +/** Return the number of scenes using (instantiating) that object in their collections. */ int BKE_object_scenes_users_get(struct Main *bmain, struct Object *ob); struct MovieClip *BKE_object_movieclip_get(struct Scene *scene, @@ -369,7 +592,16 @@ struct MovieClip *BKE_object_movieclip_get(struct Scene *scene, bool use_default); void BKE_object_runtime_reset(struct Object *object); -void BKE_object_runtime_reset_on_copy(struct Object *object, const int flag); +/** + * Reset all pointers which we don't want to be shared when copying the object. + */ +void BKE_object_runtime_reset_on_copy(struct Object *object, int flag); +/** + * The function frees memory used by the runtime data, but not the runtime field itself. + * + * All runtime data is cleared to ensure it's not used again, + * in keeping with other `_free_data(..)` functions. + */ void BKE_object_runtime_free_data(struct Object *object); void BKE_object_batch_cache_dirty_tag(struct Object *ob); @@ -393,12 +625,31 @@ typedef enum eObjectSet { OB_SET_ALL, /* All Objects. */ } eObjectSet; +/** + * Iterates over all objects of the given scene layer. + * Depending on the #eObjectSet flag: + * collect either #OB_SET_ALL, #OB_SET_VISIBLE or #OB_SET_SELECTED objects. + * If #OB_SET_VISIBLE or#OB_SET_SELECTED are collected, + * then also add related objects according to the given \a includeFilter. + */ struct LinkNode *BKE_object_relational_superset(struct ViewLayer *view_layer, eObjectSet objectSet, eObRelationTypes includeFilter); +/** + * \return All groups this object is a part of, caller must free. + */ struct LinkNode *BKE_object_groups(struct Main *bmain, struct Scene *scene, struct Object *ob); void BKE_object_groups_clear(struct Main *bmain, struct Scene *scene, struct Object *object); +/** + * 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 KD-tree or nullptr if it can't be created. + */ struct KDTree_3d *BKE_object_as_kdtree(struct Object *ob, int *r_tot); bool BKE_object_modifier_use_time(struct Scene *scene, @@ -406,6 +657,10 @@ bool BKE_object_modifier_use_time(struct Scene *scene, struct ModifierData *md, int dag_eval_mode); +/** + * \note this function should eventually be replaced by depsgraph functionality. + * Avoid calling this in new code unless there is a very good reason for it! + */ bool BKE_object_modifier_update_subframe(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, @@ -419,7 +674,8 @@ bool BKE_object_empty_image_frame_is_visible_in_view3d(const struct Object *ob, bool BKE_object_empty_image_data_is_visible_in_view3d(const struct Object *ob, const struct RegionView3D *rv3d); -/* This is an utility function for Python's object.to_mesh() (the naming is not very clear though). +/** + * This is an utility function for Python's object.to_mesh() (the naming is not very clear though). * The result is owned by the object. * * The mesh will be freed when object is re-evaluated or is destroyed. It is possible to force to @@ -436,11 +692,12 @@ struct Mesh *BKE_object_to_mesh(struct Depsgraph *depsgraph, void BKE_object_to_mesh_clear(struct Object *object); -/* This is an utility function for Python's object.to_curve(). +/** + * This is an utility function for Python's `object.to_curve()`. * The result is owned by the object. * * The curve will be freed when object is re-evaluated or is destroyed. It is possible to force - * clear memory used by this curve by calling BKE_object_to_curve_clear(). + * clear memory used by this curve by calling #BKE_object_to_curve_clear(). * * If apply_modifiers is true and the object is a curve one, then spline deform modifiers are * applied on the curve control points. @@ -458,6 +715,15 @@ void BKE_object_modifiers_lib_link_common(void *userData, struct ID **idpoin, int cb_flag); +/** + * Return the last subsurf modifier of an object, this does not check whether modifiers on top of + * it are disabled. Return NULL if no such modifier is found. + * + * This does not check if the modifier is enabled as it is assumed that the caller verified that it + * is enabled for its evaluation mode. + */ +struct SubsurfModifierData *BKE_object_get_last_subsurf_modifier(const struct Object *ob); + void BKE_object_replace_data_on_shallow_copy(struct Object *ob, struct ID *new_data); struct PartEff; diff --git a/source/blender/blenkernel/BKE_object_deform.h b/source/blender/blenkernel/BKE_object_deform.h index a10158254c2..fe7a9ddc633 100644 --- a/source/blender/blenkernel/BKE_object_deform.h +++ b/source/blender/blenkernel/BKE_object_deform.h @@ -31,24 +31,71 @@ struct MDeformVert; struct Object; struct bDeformGroup; -/* General vgroup operations */ +/* General vgroup operations. */ + +/** + * Update users of vgroups from this object, according to given map. + * + * Use it when you remove or reorder vgroups in the object. + * + * \param map: an array mapping old indices to new indices. + */ void BKE_object_defgroup_remap_update_users(struct Object *ob, const int *map); +/** + * Get #MDeformVert vgroup data from given object. Should only be used in Object mode. + * + * \return True if the id type supports weights. + */ bool BKE_object_defgroup_array_get(struct ID *id, struct MDeformVert **dvert_arr, int *dvert_tot); +/** + * Add a vgroup of default name to object. *Does not* handle #MDeformVert data at all! + */ struct bDeformGroup *BKE_object_defgroup_add(struct Object *ob); +/** + * Add a vgroup of given name to object. *Does not* handle #MDeformVert data at all! + */ struct bDeformGroup *BKE_object_defgroup_add_name(struct Object *ob, const char *name); +/** + * Create #MDeformVert data for given ID. Work in Object mode only. + */ struct MDeformVert *BKE_object_defgroup_data_create(struct ID *id); -bool BKE_object_defgroup_clear(struct Object *ob, - struct bDeformGroup *dg, - const bool use_selection); -bool BKE_object_defgroup_clear_all(struct Object *ob, const bool use_selection); +/** + * Remove all verts (or only selected ones) from given vgroup. Work in Object and Edit modes. + * + * \param use_selection: Only operate on selection. + * \return True if any vertex was removed, false otherwise. + */ +bool BKE_object_defgroup_clear(struct Object *ob, struct bDeformGroup *dg, bool use_selection); +/** + * Remove all verts (or only selected ones) from all vgroups. Work in Object and Edit modes. + * + * \param use_selection: Only operate on selection. + * \return True if any vertex was removed, false otherwise. + */ +bool BKE_object_defgroup_clear_all(struct Object *ob, bool use_selection); +/** + * Remove given vgroup from object. Work in Object and Edit modes. + */ void BKE_object_defgroup_remove(struct Object *ob, struct bDeformGroup *defgroup); +/** + * Remove all vgroups from object. Work in Object and Edit modes. + * When only_unlocked=true, locked vertex groups are not removed. + */ void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked); +/** + * Remove all vgroups from object. Work in Object and Edit modes. + */ void BKE_object_defgroup_remove_all(struct Object *ob); +/** + * Compute mapping for vertex groups with matching name, -1 is used for no remapping. + * Returns null if no remapping is required. + * The returned array has to be freed. + */ int *BKE_object_defgroup_index_map_create(struct Object *ob_src, struct Object *ob_dst, int *r_map_len); @@ -57,34 +104,69 @@ void BKE_object_defgroup_index_map_apply(struct MDeformVert *dvert, const int *map, int map_len); -/* Select helpers */ +/* Select helpers. */ + enum eVGroupSelect; +/** + * Return the subset type of the Vertex Group Selection. + */ bool *BKE_object_defgroup_subset_from_select_type(struct Object *ob, enum eVGroupSelect subset_type, int *r_defgroup_tot, int *r_subset_count); +/** + * Store indices from the defgroup_validmap (faster lookups in some cases). + */ void BKE_object_defgroup_subset_to_index_array(const bool *defgroup_validmap, - const int defgroup_tot, + int defgroup_tot, int *r_defgroup_subset_map); /* ********** */ -bool *BKE_object_defgroup_lock_flags_get(struct Object *ob, const int defbase_tot); -bool *BKE_object_defgroup_validmap_get(struct Object *ob, const int defbase_tot); +/** + * Gets the status of "flag" for each #bDeformGroup + * in the object data's vertex group list and returns an array containing them + */ +bool *BKE_object_defgroup_lock_flags_get(struct Object *ob, int defbase_tot); +bool *BKE_object_defgroup_validmap_get(struct Object *ob, int defbase_tot); +/** + * Returns total selected vgroups, + * `wpi.defbase_sel` is assumed malloc'd, all values are set. + */ bool *BKE_object_defgroup_selected_get(struct Object *ob, int defbase_tot, int *r_dg_flags_sel_tot); +/** + * Checks if the lock relative mode is applicable. + * + * \return true if an unlocked deform group is active. + */ bool BKE_object_defgroup_check_lock_relative(const bool *lock_flags, const bool *validmap, int index); +/** + * Additional check for whether the lock relative mode is applicable in multi-paint mode. + * + * \return true if none of the selected groups are locked. + */ bool BKE_object_defgroup_check_lock_relative_multi(int defbase_tot, const bool *lock_flags, const bool *selected, int sel_tot); +/** + * Takes a pair of boolean masks of all locked and all deform groups, and computes + * a pair of masks for locked deform and unlocked deform groups. Output buffers may + * reuse the input ones. + */ void BKE_object_defgroup_split_locked_validmap( int defbase_tot, const bool *locked, const bool *deform, bool *r_locked, bool *r_unlocked); +/** + * Marks mirror vgroups in output and counts them. + * Output and counter assumed to be already initialized. + * Designed to be usable after BKE_object_defgroup_selected_get to extend selection to mirror. + */ void BKE_object_defgroup_mirror_selection(struct Object *ob, int defbase_tot, const bool *selection, diff --git a/source/blender/blenkernel/BKE_ocean.h b/source/blender/blenkernel/BKE_ocean.h index 380f9045520..f598fb09773 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 @@ -73,13 +73,22 @@ typedef struct OceanCache { 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); +bool BKE_ocean_ensure(struct OceanModifierData *omd, int resolution); +/** + * 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); + int resolution); +/** + * Return true if the ocean is valid and can be used. + */ bool BKE_ocean_is_valid(const struct Ocean *o); +/** + * Return true if the ocean data is valid and can be used. + */ bool BKE_ocean_init(struct Ocean *o, int M, int N, @@ -104,15 +113,26 @@ bool BKE_ocean_init(struct Ocean *o, int seed); void BKE_ocean_simulate(struct Ocean *o, float t, float scale, float chop_amount); -/* sampling the ocean surface */ float BKE_ocean_jminus_to_foam(float jminus, float coverage); +/** + * Sampling the ocean surface. + */ void BKE_ocean_eval_uv(struct Ocean *oc, struct OceanResult *ocr, float u, float v); +/** + * Use catmullrom interpolation rather than linear. + */ void BKE_ocean_eval_uv_catrom(struct Ocean *oc, struct OceanResult *ocr, float u, float v); void BKE_ocean_eval_xz(struct Ocean *oc, struct OceanResult *ocr, float x, float z); void BKE_ocean_eval_xz_catrom(struct Ocean *oc, struct OceanResult *ocr, float x, float z); +/** + * Note that this doesn't wrap properly for i, j < 0, but its not really meant for that being + * just a way to get the raw data out to save in some image format. + */ void BKE_ocean_eval_ij(struct Ocean *oc, struct OceanResult *ocr, int i, int j); -/* ocean cache handling */ +/** + * Ocean cache handling. + */ struct OceanCache *BKE_ocean_init_cache(const char *bakepath, const char *relbase, int start, @@ -136,9 +156,27 @@ void BKE_ocean_free_cache(struct OceanCache *och); void BKE_ocean_free_modifier_cache(struct OceanModifierData *omd); /* ocean_spectrum.c */ -float BLI_ocean_spectrum_piersonmoskowitz(const struct Ocean *oc, const float kx, const float kz); -float BLI_ocean_spectrum_texelmarsenarsloe(const struct Ocean *oc, const float kx, const float kz); -float BLI_ocean_spectrum_jonswap(const struct Ocean *oc, const float kx, const float kz); + +/** + * Pierson-Moskowitz model, 1964, assumes waves reach equilibrium with wind. + * Model is intended for large area 'fully developed' sea, where winds have been steadily blowing + * for days over an area that includes hundreds of wavelengths on a side. + */ +float BLI_ocean_spectrum_piersonmoskowitz(const struct Ocean *oc, float kx, float kz); +/** + * TMA extends the JONSWAP spectrum. + * This spectral model is best suited to shallow water. + */ +float BLI_ocean_spectrum_texelmarsenarsloe(const struct Ocean *oc, float kx, float kz); +/** + * Hasselmann et al, 1973. This model extends the Pierson-Moskowitz model with a peak sharpening + * function This enhancement is an artificial construct to address the problem that the wave + * spectrum is never fully developed. + * + * The fetch parameter represents the distance from a lee shore, + * called the fetch, or the distance over which the wind blows with constant velocity. + */ +float BLI_ocean_spectrum_jonswap(const struct Ocean *oc, float kx, float kz); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_packedFile.h b/source/blender/blenkernel/BKE_packedFile.h index 8cb0c78d9aa..87a46f5f888 100644 --- a/source/blender/blenkernel/BKE_packedFile.h +++ b/source/blender/blenkernel/BKE_packedFile.h @@ -57,17 +57,32 @@ enum ePF_FileStatus { PF_ASK = 10, }; -/* pack */ +/* Pack. */ + struct PackedFile *BKE_packedfile_duplicate(const struct PackedFile *pf_src); struct PackedFile *BKE_packedfile_new(struct ReportList *reports, const char *filename, const char *basepath); struct PackedFile *BKE_packedfile_new_from_memory(void *mem, int memlen); +/** + * No libraries for now. + */ void BKE_packedfile_pack_all(struct Main *bmain, struct ReportList *reports, bool verbose); void BKE_packedfile_pack_all_libraries(struct Main *bmain, struct ReportList *reports); -/* unpack */ +/* Unpack. */ + +/** + * #BKE_packedfile_unpack_to_file() looks at the existing files (abs_name, local_name) + * and a packed file. + * + * It returns a char *to the existing file name / new file name or NULL when + * there was an error or when the user decides to cancel the operation. + * + * \warning 'abs_name' may be relative still! (use a "//" prefix) + * be sure to run #BLI_path_abs on it first. + */ char *BKE_packedfile_unpack_to_file(struct ReportList *reports, const char *ref_file_name, const char *abs_name, @@ -105,25 +120,40 @@ int BKE_packedfile_write_to_file(struct ReportList *reports, const char *ref_file_name, const char *filename, struct PackedFile *pf, - const bool guimode); + bool guimode); + +/* Free. */ -/* free */ void BKE_packedfile_free(struct PackedFile *pf); -/* info */ +/* Info. */ + int BKE_packedfile_count_all(struct Main *bmain); +/** + * This function compares a packed file to a 'real' file. + * It returns an integer indicating if: + * + * - #PF_EQUAL: the packed file and original file are identical. + * - #PF_DIFFERENT: the packed file and original file differ. + * - #PF_NOFILE: the original file doesn't exist. + */ enum ePF_FileCompare BKE_packedfile_compare_to_file(const char *ref_file_name, const char *filename, struct PackedFile *pf); -/* read */ +/* Read. */ + 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, throws error when ID is Library */ +/** + * 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, struct ReportList *reports, diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 73413b61456..4019c4d62c4 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -111,10 +111,11 @@ typedef enum ePaintOverlayControlFlags { (PAINT_OVERLAY_OVERRIDE_SECONDARY | PAINT_OVERLAY_OVERRIDE_PRIMARY | \ PAINT_OVERLAY_OVERRIDE_CURSOR) -/* Defines 8 areas resulting of splitting the object space by the XYZ axis planes. This is used to +/** + * Defines 8 areas resulting of splitting the object space by the XYZ axis planes. This is used to * flip or mirror transform values depending on where the vertex is and where the transform - * operation started to support XYZ symmetry on those operations in a predictable way. */ - + * operation started to support XYZ symmetry on those operations in a predictable way. + */ #define PAINT_SYMM_AREA_DEFAULT 0 typedef enum ePaintSymmetryAreas { @@ -136,29 +137,42 @@ ePaintOverlayControlFlags BKE_paint_get_overlay_flags(void); void BKE_paint_reset_overlay_invalid(ePaintOverlayControlFlags flag); void BKE_paint_set_overlay_override(enum eOverlayFlags flag); -/* palettes */ +/* Palettes. */ + struct Palette *BKE_palette_add(struct Main *bmain, const char *name); struct PaletteColor *BKE_palette_color_add(struct Palette *palette); bool BKE_palette_is_empty(const struct Palette *palette); +/** + * Remove color from palette. Must be certain color is inside the palette! + */ void BKE_palette_color_remove(struct Palette *palette, struct PaletteColor *color); void BKE_palette_clear(struct Palette *palette); -void BKE_palette_sort_hsv(struct tPaletteColorHSV *color_array, const int totcol); -void BKE_palette_sort_svh(struct tPaletteColorHSV *color_array, const int totcol); -void BKE_palette_sort_vhs(struct tPaletteColorHSV *color_array, const int totcol); -void BKE_palette_sort_luminance(struct tPaletteColorHSV *color_array, const int totcol); +void BKE_palette_sort_hsv(struct tPaletteColorHSV *color_array, int totcol); +void BKE_palette_sort_svh(struct tPaletteColorHSV *color_array, int totcol); +void BKE_palette_sort_vhs(struct tPaletteColorHSV *color_array, int totcol); +void BKE_palette_sort_luminance(struct tPaletteColorHSV *color_array, int totcol); bool BKE_palette_from_hash(struct Main *bmain, struct GHash *color_table, const char *name, - const bool linear); + bool linear); + +/* Paint curves. */ -/* paint curves */ struct PaintCurve *BKE_paint_curve_add(struct Main *bmain, const char *name); +/** + * Call when entering each respective paint mode. + */ bool BKE_paint_ensure(struct ToolSettings *ts, struct Paint **r_paint); void BKE_paint_init(struct Main *bmain, struct Scene *sce, ePaintMode mode, const char col[3]); void BKE_paint_free(struct Paint *p); -void BKE_paint_copy(struct Paint *src, struct Paint *tar, const int flag); +/** + * Called when copying scene settings, so even if 'src' and 'tar' are the same still do a + * #id_us_plus(), rather than if we were copying between 2 existing scenes where a matching + * value should decrease the existing user count as with #paint_brush_set() + */ +void BKE_paint_copy(struct Paint *src, struct Paint *tar, int flag); void BKE_paint_runtime_init(const struct ToolSettings *ts, struct Paint *paint); @@ -169,7 +183,7 @@ bool BKE_paint_ensure_from_paintmode(struct Scene *sce, ePaintMode mode); struct Paint *BKE_paint_get_active_from_paintmode(struct Scene *sce, ePaintMode mode); const struct EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(ePaintMode mode); const char *BKE_paint_get_tool_prop_id_from_paintmode(ePaintMode mode); -uint BKE_paint_get_brush_tool_offset_from_paintmode(const ePaintMode mode); +uint BKE_paint_get_brush_tool_offset_from_paintmode(ePaintMode mode); struct Paint *BKE_paint_get_active(struct Scene *sce, struct ViewLayer *view_layer); struct Paint *BKE_paint_get_active_from_context(const struct bContext *C); ePaintMode BKE_paintmode_get_active_from_context(const struct bContext *C); @@ -179,28 +193,48 @@ void BKE_paint_brush_set(struct Paint *paint, struct Brush *br); struct Palette *BKE_paint_palette(struct Paint *paint); void BKE_paint_palette_set(struct Paint *p, struct Palette *palette); void BKE_paint_curve_set(struct Brush *br, struct PaintCurve *pc); -void BKE_paint_curve_clamp_endpoint_add_index(struct PaintCurve *pc, const int add_index); +void BKE_paint_curve_clamp_endpoint_add_index(struct PaintCurve *pc, int add_index); -/* testing face select mode - * Texture paint could be removed since selected faces are not used - * however hiding faces is useful */ +/** + * Return true when in vertex/weight/texture paint + face-select mode? + */ bool BKE_paint_select_face_test(struct Object *ob); +/** + * Return true when in vertex/weight paint + vertex-select mode? + */ bool BKE_paint_select_vert_test(struct Object *ob); +/** + * used to check if selection is possible + * (when we don't care if its face or vert) + */ bool BKE_paint_select_elem_test(struct Object *ob); -/* partial visibility */ +/* Partial visibility. */ + +/** + * Returns non-zero if any of the face's vertices are hidden, zero otherwise. + */ bool paint_is_face_hidden(const struct MLoopTri *lt, const struct MVert *mvert, const struct MLoop *mloop); +/** + * Returns non-zero if any of the corners of the grid + * face whose inner corner is at (x, y) are hidden, zero otherwise. + */ bool paint_is_grid_face_hidden(const unsigned int *grid_hidden, int gridsize, int x, int y); +/** + * Return true if all vertices in the face are visible, false otherwise. + */ bool paint_is_bmesh_face_hidden(struct BMFace *f); -/* paint masks */ +/* Paint masks. */ + float paint_grid_paint_mask(const struct GridPaintMask *gpm, uint level, uint x, uint y); -void BKE_paint_face_set_overlay_color_get(const int face_set, const int seed, uchar r_color[4]); +void BKE_paint_face_set_overlay_color_get(int face_set, int seed, uchar r_color[4]); + +/* Stroke related. */ -/* stroke related */ bool paint_calculate_rake_rotation(struct UnifiedPaintSettings *ups, struct Brush *brush, const float mouse_pos[2]); @@ -211,14 +245,20 @@ void paint_update_brush_rake_rotation(struct UnifiedPaintSettings *ups, void BKE_paint_stroke_get_average(struct Scene *scene, struct Object *ob, float stroke[3]); /* Tool slot API. */ + void BKE_paint_toolslots_init_from_main(struct Main *bmain); void BKE_paint_toolslots_len_ensure(struct Paint *paint, int len); void BKE_paint_toolslots_brush_update_ex(struct Paint *paint, struct Brush *brush); void BKE_paint_toolslots_brush_update(struct Paint *paint); +/** + * Run this to ensure brush types are set for each slot on entering modes + * (for new scenes for example). + */ void BKE_paint_toolslots_brush_validate(struct Main *bmain, struct Paint *paint); struct Brush *BKE_paint_toolslots_brush_get(struct Paint *paint, int slot_index); /* .blend I/O */ + void BKE_paint_blend_write(struct BlendWriter *writer, struct Paint *paint); void BKE_paint_blend_read_data(struct BlendDataReader *reader, const struct Scene *scene, @@ -229,7 +269,7 @@ void BKE_paint_blend_read_lib(struct BlendLibReader *reader, #define SCULPT_FACE_SET_NONE 0 -/* Used for both vertex color and weight paint */ +/** Used for both vertex color and weight paint. */ struct SculptVertexPaintGeomMap { int *vert_map_mem; struct MeshElemMap *vert_to_loop; @@ -237,7 +277,7 @@ struct SculptVertexPaintGeomMap { struct MeshElemMap *vert_to_poly; }; -/* Pose Brush IK Chain */ +/** Pose Brush IK Chain. */ typedef struct SculptPoseIKChainSegment { float orig[3]; float head[3]; @@ -459,6 +499,7 @@ typedef struct SculptSession { /* These are always assigned to base mesh data when using PBVH_FACES and PBVH_GRIDS. */ struct MVert *mvert; + const float (*vert_normals)[3]; struct MPoly *mpoly; struct MLoop *mloop; @@ -538,7 +579,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]; @@ -576,10 +617,6 @@ typedef struct SculptSession { float init_pivot_rot[4]; float init_pivot_scale[3]; - float prev_pivot_pos[3]; - float prev_pivot_rot[4]; - float prev_pivot_scale[3]; - union { struct { struct SculptVertexPaintGeomMap gmap; @@ -620,10 +657,15 @@ void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss); void BKE_sculptsession_bm_to_me(struct Object *ob, bool reorder); void BKE_sculptsession_bm_to_me_for_render(struct Object *object); -/* Create new color layer on object if it doesn't have one and if experimental feature set has - * sculpt vertex color enabled. Returns truth if new layer has been added, false otherwise. */ +/** + * Create new color layer on object if it doesn't have one and if experimental feature set has + * sculpt vertex color enabled. Returns truth if new layer has been added, false otherwise. + */ void BKE_sculpt_color_layer_create_if_needed(struct Object *object); +/** + * \warning Expects a fully evaluated depsgraph. + */ void BKE_sculpt_update_object_for_edit(struct Depsgraph *depsgraph, struct Object *ob_orig, bool need_pmap, @@ -632,6 +674,10 @@ void BKE_sculpt_update_object_for_edit(struct Depsgraph *depsgraph, void BKE_sculpt_update_object_before_eval(struct Object *ob_eval); void BKE_sculpt_update_object_after_eval(struct Depsgraph *depsgraph, struct Object *ob_eval); +/** + * Sculpt mode handles multi-res differently from regular meshes, but only if + * it's the last modifier on the stack and it is not on the first level. + */ struct MultiresModifierData *BKE_sculpt_multires_active(struct Scene *scene, struct Object *ob); int BKE_sculpt_mask_layers_ensure(struct Object *ob, struct MultiresModifierData *mmd); void BKE_sculpt_toolsettings_data_ensure(struct Scene *scene); @@ -640,19 +686,37 @@ struct PBVH *BKE_sculpt_object_pbvh_ensure(struct Depsgraph *depsgraph, struct O void BKE_sculpt_bvh_update_from_ccg(struct PBVH *pbvh, struct SubdivCCG *subdiv_ccg); -/* This ensure that all elements in the mesh (both vertices and grids) have their visibility - * updated according to the face sets. */ +/** + * This ensure that all elements in the mesh (both vertices and grids) have their visibility + * updated according to the face sets. + */ void BKE_sculpt_sync_face_set_visibility(struct Mesh *mesh, struct SubdivCCG *subdiv_ccg); -/* Individual function to sync the Face Set visibility to mesh and grids. */ +/** + * Individual function to sync the Face Set visibility to mesh and grids. + */ void BKE_sculpt_sync_face_sets_visibility_to_base_mesh(struct Mesh *mesh); void BKE_sculpt_sync_face_sets_visibility_to_grids(struct Mesh *mesh, struct SubdivCCG *subdiv_ccg); +/** + * Ensures that a Face Set data-layers exists. If it does not, it creates one respecting the + * visibility stored in the vertices of the mesh. If it does, it copies the visibility from the + * mesh to the Face Sets. */ void BKE_sculpt_face_sets_ensure_from_base_mesh_visibility(struct Mesh *mesh); +/** + * Ensures we do have expected mesh data in original mesh for the sculpt mode. + * + * \note IDs are expected to be original ones here, and calling code should ensure it updates its + * depsgraph properly after calling this function if it needs up-to-date evaluated data. + */ void BKE_sculpt_ensure_orig_mesh_data(struct Scene *scene, struct Object *object); +/** + * Test if PBVH can be used directly for drawing, which is faster than + * drawing the mesh and all updates that come with it. + */ bool BKE_sculptsession_use_pbvh_draw(const struct Object *ob, const struct View3D *v3d); enum { diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h index 78a6e47ec48..804331a3412 100644 --- a/source/blender/blenkernel/BKE_particle.h +++ b/source/blender/blenkernel/BKE_particle.h @@ -286,18 +286,24 @@ BLI_INLINE void psys_frand_vec(ParticleSystem *psys, unsigned int seed, float ve /* ----------- functions needed outside particlesystem ---------------- */ /* particle.c */ + +/* Few helpers for count-all etc. */ + int count_particles(struct ParticleSystem *psys); int count_particles_mod(struct ParticleSystem *psys, int totgr, int cur); int psys_get_child_number(struct Scene *scene, struct ParticleSystem *psys, - const bool use_render_params); -int psys_get_tot_child(struct Scene *scene, - struct ParticleSystem *psys, - const bool use_render_params); + bool use_render_params); +int psys_get_tot_child(struct Scene *scene, struct ParticleSystem *psys, bool use_render_params); +/** + * Get object's active particle system safely. + */ struct ParticleSystem *psys_get_current(struct Object *ob); -/* for rna */ + +/* For RNA API. */ + short psys_get_current_num(struct Object *ob); void psys_set_current_num(struct Object *ob, int index); /* UNUSED */ @@ -305,22 +311,23 @@ void psys_set_current_num(struct Object *ob, int index); struct LatticeDeformData *psys_create_lattice_deform_data(struct ParticleSimulationData *sim); -/* For a given evaluated particle system get its original. +/** + * For a given evaluated particle system get its original. * - * If this input is an original particle system already, the return value is the - * same as the input. */ + * If this input is an original particle system already, the return value is the same as the input. + */ struct ParticleSystem *psys_orig_get(struct ParticleSystem *psys); -/* For a given original object and its particle system, get evaluated particle - * system within a given dependency graph. */ +/** + * For a given original object and its particle system, + * get evaluated particle system within a given dependency graph. + */ struct ParticleSystem *psys_eval_get(struct Depsgraph *depsgraph, struct Object *object, struct ParticleSystem *psys); bool psys_in_edit_mode(struct Depsgraph *depsgraph, const struct ParticleSystem *psys); -bool psys_check_enabled(struct Object *ob, - struct ParticleSystem *psys, - const bool use_render_params); +bool psys_check_enabled(struct Object *ob, struct ParticleSystem *psys, bool use_render_params); bool psys_check_edited(struct ParticleSystem *psys); void psys_find_group_weights(struct ParticleSettings *part); @@ -328,11 +335,17 @@ void psys_check_group_weights(struct ParticleSettings *part); int psys_uses_gravity(struct ParticleSimulationData *sim); void BKE_particlesettings_fluid_default_settings(struct ParticleSettings *part); -/* free */ +/** + * Free cache path. + */ void psys_free_path_cache(struct ParticleSystem *psys, struct PTCacheEdit *edit); +/** + * Free everything. + */ void psys_free(struct Object *ob, struct ParticleSystem *psys); - -/* Copy. */ +/** + * Copy. + */ void psys_copy_particles(struct ParticleSystem *psys_dst, struct ParticleSystem *psys_src); bool psys_render_simplify_params(struct ParticleSystem *psys, @@ -375,23 +388,27 @@ void object_remove_particle_system(struct Main *bmain, struct ParticleSettings *BKE_particlesettings_add(struct Main *bmain, const char *name); void psys_reset(struct ParticleSystem *psys, int mode); -void psys_find_parents(struct ParticleSimulationData *sim, const bool use_render_params); +void psys_find_parents(struct ParticleSimulationData *sim, bool use_render_params); void psys_unique_name(struct Object *object, struct ParticleSystem *psys, const char *defname); -void psys_cache_paths(struct ParticleSimulationData *sim, - float cfra, - const bool use_render_params); +/** + * Calculates paths ready for drawing/rendering + * - Useful for making use of opengl vertex arrays for super fast strand drawing. + * - Makes child strands possible and creates them too into the cache. + * - Cached path data is also used to determine cut position for the edit-mode tool. + */ +void psys_cache_paths(struct ParticleSimulationData *sim, float cfra, bool use_render_params); void psys_cache_edit_paths(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct PTCacheEdit *edit, float cfra, - const bool use_render_params); + bool use_render_params); void psys_cache_child_paths(struct ParticleSimulationData *sim, float cfra, - const bool editupdate, - const bool use_render_params); + bool editupdate, + bool use_render_params); int do_guides(struct Depsgraph *depsgraph, struct ParticleSettings *part, struct ListBase *effectors, @@ -409,16 +426,24 @@ float psys_get_child_size(struct ParticleSystem *psys, struct ChildParticle *cpa, float cfra, float *pa_time); +/** + * Gets hair (or keyed) particles state at the "path time" specified in `state->time`. + */ void psys_get_particle_on_path(struct ParticleSimulationData *sim, int pa_num, struct ParticleKey *state, - const bool vel); -int psys_get_particle_state(struct ParticleSimulationData *sim, - int p, - struct ParticleKey *state, - int always); + bool vel); +/** + * Gets particle's state at a time. + * \return true if particle exists and can be seen and false if not. + */ +bool psys_get_particle_state(struct ParticleSimulationData *sim, + int p, + struct ParticleKey *state, + bool always); + +/* Child paths. */ -/* child paths */ void BKE_particlesettings_clump_curve_init(struct ParticleSettings *part); void BKE_particlesettings_rough_curve_init(struct ParticleSettings *part); void BKE_particlesettings_twist_curve_init(struct ParticleSettings *part); @@ -434,9 +459,13 @@ void psys_apply_child_modifiers(struct ParticleThreadContext *ctx, void psys_sph_init(struct ParticleSimulationData *sim, struct SPHData *sphdata); void psys_sph_finalize(struct SPHData *sphdata); +/** + * Sample the density field at a point in space. + */ void psys_sph_density(struct BVHTree *tree, struct SPHData *data, float co[3], float vars[2]); -/* for anim.c */ +/* For anim.c */ + void psys_get_dupli_texture(struct ParticleSystem *psys, struct ParticleSettings *part, struct ParticleSystemModifierData *psmd, @@ -451,6 +480,9 @@ void psys_get_dupli_path_transform(struct ParticleSimulationData *sim, float mat[4][4], float *scale); +/** + * Threaded child particle distribution and path caching. + */ void psys_thread_context_init(struct ParticleThreadContext *ctx, struct ParticleSimulationData *sim); void psys_thread_context_free(struct ParticleThreadContext *ctx); @@ -467,9 +499,16 @@ void psys_apply_hair_lattice(struct Depsgraph *depsgraph, struct ParticleSystem *psys); /* particle_system.c */ + struct ParticleSystem *psys_get_target_system(struct Object *ob, struct ParticleTarget *pt); +/** + * Counts valid keyed targets. + */ void psys_count_keyed_targets(struct ParticleSimulationData *sim); void psys_update_particle_tree(struct ParticleSystem *psys, float cfra); +/** + * System type has changed so set sensible defaults and clear non applicable flags. + */ void psys_changed_type(struct Object *ob, struct ParticleSystem *psys); void psys_make_temp_pointcache(struct Object *ob, struct ParticleSystem *psys); @@ -486,13 +525,19 @@ void psys_get_birth_coords(struct ParticleSimulationData *sim, float dtime, float cfra); +/** + * Main particle update call, checks that things are ok on the large scale and + * then advances in to actual particle calculations depending on particle type. + */ void particle_system_update(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, struct ParticleSystem *psys, - const bool use_render_params); + bool use_render_params); -/* Callback format for performing operations on ID-pointers for particle systems */ +/** + * Callback format for performing operations on ID-pointers for particle systems. + */ typedef void (*ParticleSystemIDFunc)(struct ParticleSystem *psys, struct ID **idpoin, void *userdata, @@ -502,11 +547,15 @@ void BKE_particlesystem_id_loop(struct ParticleSystem *psys, ParticleSystemIDFunc func, void *userdata); -/* Reset all particle systems in the given object. */ +/** + * Reset all particle systems in the given object. + */ void BKE_particlesystem_reset_all(struct Object *object); /* ----------- functions needed only inside particlesystem ------------ */ + /* particle.c */ + void psys_disable_all(struct Object *ob); void psys_enable_all(struct Object *ob); @@ -544,7 +593,11 @@ void psys_get_texture(struct ParticleSimulationData *sim, struct ParticleTexture *ptex, int event, float cfra); +/** + * Interpolate a location on a face based on face coordinates. + */ void psys_interpolate_face(struct MVert *mvert, + const float (*vert_normals)[3], struct MFace *mface, struct MTFace *tface, float (*orcodata)[3], @@ -561,11 +614,16 @@ float psys_particle_value_from_verts(struct Mesh *mesh, void psys_get_from_key( struct ParticleKey *key, float loc[3], float vel[3], float rot[4], float *time); -/* BLI_bvhtree_ray_cast callback */ +/** + * Callback for #BVHTree near test. + */ void BKE_psys_collision_neartest_cb(void *userdata, int index, const struct BVHTreeRay *ray, struct BVHTreeRayHit *hit); +/** + * Interprets particle data to get a point on a mesh in object space. + */ void psys_particle_on_dm(struct Mesh *mesh_final, int from, int index, @@ -579,25 +637,43 @@ void psys_particle_on_dm(struct Mesh *mesh_final, float orco[3]); /* particle_system.c */ + void distribute_particles(struct ParticleSimulationData *sim, int from); +/** + * Set particle parameters that don't change during particle's life. + */ void init_particle(struct ParticleSimulationData *sim, struct ParticleData *pa); void psys_calc_dmcache(struct Object *ob, struct Mesh *mesh_final, struct Mesh *mesh_original, struct ParticleSystem *psys); +/** + * Find the final derived mesh tessface for a particle, from its original tessface index. + * This is slow and can be optimized but only for many lookups. + * + * \param mesh_final: Final mesh, it may not have the same topology as original mesh. + * \param mesh_original: Original mesh, use for accessing #MPoly to #MFace mapping. + * \param findex_orig: The input tessface index. + * \param fw: Face weights (position of the particle inside the \a findex_orig tessface). + * \param poly_nodes: May be NULL, otherwise an array of linked list, + * one for each final \a mesh_final polygon, containing all its tessfaces indices. + * \return The \a mesh_final tessface index. + */ int psys_particle_dm_face_lookup(struct Mesh *mesh_final, struct Mesh *mesh_original, - int findex, + int findex_orig, const float fw[4], struct LinkNode **poly_nodes); +/** + * Sets particle to the emitter surface with initial velocity & rotation. + */ void reset_particle(struct ParticleSimulationData *sim, struct ParticleData *pa, float dtime, float cfra); -float psys_get_current_display_percentage(struct ParticleSystem *psys, - const bool use_render_params); +float psys_get_current_display_percentage(struct ParticleSystem *psys, bool use_render_params); /* psys_reset */ #define PSYS_RESET_ALL 1 @@ -629,6 +705,7 @@ extern void (*BKE_particle_batch_cache_dirty_tag_cb)(struct ParticleSystem *psys extern void (*BKE_particle_batch_cache_free_cb)(struct ParticleSystem *psys); /* .blend file I/O */ + void BKE_particle_partdeflect_blend_read_data(struct BlendDataReader *reader, struct PartDeflect *pd); void BKE_particle_partdeflect_blend_read_lib(struct BlendLibReader *reader, diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index 056a7e2d897..1ef1c98ce83 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -90,7 +90,9 @@ void BKE_pbvh_get_frustum_planes(PBVH *pbvh, PBVHFrustumPlanes *planes); /* Callbacks */ -/* returns 1 if the search should continue from this node, 0 otherwise */ +/** + * Returns true if the search should continue from this node, false otherwise. + */ typedef bool (*BKE_pbvh_SearchCallback)(PBVHNode *node, void *data); typedef void (*BKE_pbvh_HitCallback)(PBVHNode *node, void *data); @@ -101,8 +103,14 @@ typedef void (*BKE_pbvh_SearchNearestCallback)(PBVHNode *node, void *data, float /* Building */ PBVH *BKE_pbvh_new(void); +/** + * Do a full rebuild with on Mesh data structure. + * + * \note Unlike mpoly/mloop/verts, looptri is *totally owned* by PBVH + * (which means it may rewrite it if needed, see #BKE_pbvh_vert_coords_apply(). + */ void BKE_pbvh_build_mesh(PBVH *pbvh, - const struct Mesh *mesh, + struct Mesh *mesh, const struct MPoly *mpoly, const struct MLoop *mloop, struct MVert *verts, @@ -112,6 +120,9 @@ void BKE_pbvh_build_mesh(PBVH *pbvh, struct CustomData *pdata, const struct MLoopTri *looptri, int looptri_num); +/** + * Do a full rebuild with on Grids data structure. + */ void BKE_pbvh_build_grids(PBVH *pbvh, struct CCGElem **grids, int totgrid, @@ -119,17 +130,20 @@ void BKE_pbvh_build_grids(PBVH *pbvh, void **gridfaces, struct DMFlagMat *flagmats, unsigned int **grid_hidden); +/** + * Build a PBVH from a BMesh. + */ void BKE_pbvh_build_bmesh(PBVH *pbvh, struct BMesh *bm, bool smooth_shading, struct BMLog *log, - const int cd_vert_node_offset, - const int cd_face_node_offset); + int cd_vert_node_offset, + int cd_face_node_offset); 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 +154,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 */ @@ -170,8 +184,10 @@ bool BKE_pbvh_bmesh_node_raycast_detail(PBVHNode *node, float *depth, float *r_edge_length); -/* for orthographic cameras, project the far away ray segment points to the root node so - * we can have better precision. */ +/** + * For orthographic cameras, project the far away ray segment points to the root node so + * we can have better precision. + */ void BKE_pbvh_raycast_project_ray_root( PBVH *pbvh, bool original, float ray_start[3], float ray_end[3], float ray_normal[3]); @@ -215,12 +231,19 @@ typedef enum { PBVHType BKE_pbvh_type(const PBVH *pbvh); bool BKE_pbvh_has_faces(const PBVH *pbvh); -/* Get the PBVH root's bounding box */ +/** + * Get the PBVH root's bounding box. + */ void BKE_pbvh_bounding_box(const PBVH *pbvh, float min[3], float max[3]); -/* multires hidden data, only valid for type == PBVH_GRIDS */ +/** + * Multi-res hidden data, only valid for type == PBVH_GRIDS. + */ unsigned int **BKE_pbvh_grid_hidden(const PBVH *pbvh); +/** + * Returns the number of visible quads in the nodes' grids. + */ int BKE_pbvh_count_grid_quads(BLI_bitmap **grid_hidden, const int *grid_indices, int totgrid, @@ -228,7 +251,9 @@ int BKE_pbvh_count_grid_quads(BLI_bitmap **grid_hidden, void BKE_pbvh_sync_face_sets_to_grids(PBVH *pbvh); -/* multires level, only valid for type == PBVH_GRIDS */ +/** + * Multi-res level, only valid for type == #PBVH_GRIDS. + */ const struct CCGKey *BKE_pbvh_get_grid_key(const PBVH *pbvh); struct CCGElem **BKE_pbvh_get_grids(const PBVH *pbvh); @@ -236,7 +261,9 @@ BLI_bitmap **BKE_pbvh_get_grid_visibility(const PBVH *pbvh); int BKE_pbvh_get_grid_num_vertices(const PBVH *pbvh); int BKE_pbvh_get_grid_num_faces(const PBVH *pbvh); -/* Only valid for type == PBVH_BMESH */ +/** + * Only valid for type == #PBVH_BMESH. + */ struct BMesh *BKE_pbvh_get_bmesh(PBVH *pbvh); void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size); @@ -244,13 +271,16 @@ typedef enum { PBVH_Subdivide = 1, PBVH_Collapse = 2, } PBVHTopologyUpdateMode; +/** + * Collapse short edges, subdivide long edges. + */ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, PBVHTopologyUpdateMode mode, const float center[3], const float view_normal[3], float radius, - const bool use_frontface, - const bool use_projected); + bool use_frontface, + bool use_projected); /* Node Access */ @@ -287,18 +317,28 @@ void BKE_pbvh_node_get_original_BB(PBVHNode *node, float bb_min[3], float bb_max float BKE_pbvh_node_get_tmin(PBVHNode *node); -/* test if AABB is at least partially inside the PBVHFrustumPlanes volume */ +/** + * Test if AABB is at least partially inside the #PBVHFrustumPlanes volume. + */ bool BKE_pbvh_node_frustum_contain_AABB(PBVHNode *node, void *frustum); -/* test if AABB is at least partially outside the PBVHFrustumPlanes volume */ +/** + * Test if AABB is at least partially outside the #PBVHFrustumPlanes volume. + */ bool BKE_pbvh_node_frustum_exclude_AABB(PBVHNode *node, void *frustum); struct GSet *BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node); struct GSet *BKE_pbvh_bmesh_node_other_verts(PBVHNode *node); struct GSet *BKE_pbvh_bmesh_node_faces(PBVHNode *node); +/** + * In order to perform operations on the original node coordinates + * (currently just ray-cast), store the node's triangles and vertices. + * + * Skips triangles that are hidden. + */ void BKE_pbvh_bmesh_node_save_orig(struct BMesh *bm, PBVHNode *node); void BKE_pbvh_bmesh_after_stroke(PBVH *pbvh); -/* Update Bounding Box/Redraw and clear flags */ +/* Update Bounding Box/Redraw and clear flags. */ void BKE_pbvh_update_bounds(PBVH *pbvh, int flags); void BKE_pbvh_update_vertex_data(PBVH *pbvh, int flags); @@ -318,14 +358,15 @@ void BKE_pbvh_face_sets_color_set(PBVH *pbvh, int seed, int color_default); void BKE_pbvh_respect_hide_set(PBVH *pbvh, bool respect_hide); -/* vertex deformer */ +/* Vertex Deformer. */ + float (*BKE_pbvh_vert_coords_alloc(struct PBVH *pbvh))[3]; -void BKE_pbvh_vert_coords_apply(struct PBVH *pbvh, const float (*vertCos)[3], const int totvert); +void BKE_pbvh_vert_coords_apply(struct PBVH *pbvh, const float (*vertCos)[3], int totvert); bool BKE_pbvh_is_deformed(struct PBVH *pbvh); -/* Vertex Iterator */ +/* Vertex Iterator. */ -/* this iterator has quite a lot of code, but it's designed to: +/* This iterator has quite a lot of code, but it's designed to: * - allow the compiler to eliminate dead code and variables * - spend most of the time in the relatively simple inner loop */ @@ -356,6 +397,7 @@ typedef struct PBVHVertexIter { /* mesh */ struct MVert *mverts; + float (*vert_normals)[3]; int totvert; const int *vert_indices; struct MPropCol *vcol; @@ -372,7 +414,7 @@ typedef struct PBVHVertexIter { struct MVert *mvert; struct BMVert *bm_vert; float *co; - short *no; + float *no; float *fno; float *mask; float *col; @@ -426,7 +468,7 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m BLI_assert(vi.visible); \ } \ vi.co = vi.mvert->co; \ - vi.no = vi.mvert->no; \ + vi.no = vi.vert_normals[vi.vert_indices[vi.gx]]; \ vi.index = vi.vert_indices[vi.i]; \ if (vi.vmask) { \ vi.mask = &vi.vmask[vi.index]; \ @@ -469,6 +511,11 @@ void BKE_pbvh_node_get_bm_orco_data(PBVHNode *node, int *r_orco_tris_num, float (**r_orco_coords)[3]); +/** + * \note doing a full search on all vertices here seems expensive, + * however this is important to avoid having to recalculate bound-box & sync the buffers to the + * GPU (which is far more expensive!) See: T47232. + */ bool BKE_pbvh_node_vert_update_check_any(PBVH *pbvh, PBVHNode *node); // void BKE_pbvh_node_BB_reset(PBVHNode *node); @@ -480,12 +527,14 @@ void pbvh_show_mask_set(PBVH *pbvh, bool show_mask); bool pbvh_has_face_sets(PBVH *pbvh); void pbvh_show_face_sets_set(PBVH *pbvh, bool show_face_sets); -/* Parallelization */ +/* Parallelization. */ + void BKE_pbvh_parallel_range_settings(struct TaskParallelSettings *settings, bool use_threading, int totnode); struct MVert *BKE_pbvh_get_verts(const PBVH *pbvh); +const float (*BKE_pbvh_get_vert_normals(const PBVH *pbvh))[3]; PBVHColorBufferNode *BKE_pbvh_node_color_buffer_get(PBVHNode *node); void BKE_pbvh_node_color_buffer_free(PBVH *pbvh); diff --git a/source/blender/blenkernel/BKE_pointcache.h b/source/blender/blenkernel/BKE_pointcache.h index c83fca767a1..0749b9d6d49 100644 --- a/source/blender/blenkernel/BKE_pointcache.h +++ b/source/blender/blenkernel/BKE_pointcache.h @@ -273,19 +273,28 @@ typedef struct PTCacheEdit { int totpoint, totframes, totcached, edited; } PTCacheEdit; -/* Particle functions */ void BKE_ptcache_make_particle_key(struct ParticleKey *key, int index, void **data, float time); /**************** Creating ID's ****************************/ + void BKE_ptcache_id_from_softbody(PTCacheID *pid, struct Object *ob, struct SoftBody *sb); void BKE_ptcache_id_from_particles(PTCacheID *pid, struct Object *ob, struct ParticleSystem *psys); void BKE_ptcache_id_from_cloth(PTCacheID *pid, struct Object *ob, struct ClothModifierData *clmd); +/** + * The fluid modifier does not actually use this anymore, but some parts of Blender expect that it + * still has a point cache currently. For example, the fluid modifier uses + * #DEG_add_collision_relations, which internally creates relations with the point cache. + */ void BKE_ptcache_id_from_smoke(PTCacheID *pid, struct Object *ob, struct FluidModifierData *fmd); void BKE_ptcache_id_from_dynamicpaint(PTCacheID *pid, struct Object *ob, struct DynamicPaintSurface *surface); void BKE_ptcache_id_from_rigidbody(PTCacheID *pid, struct Object *ob, struct RigidBodyWorld *rbw); +/** + * \param ob: Optional, may be NULL. + * \param scene: Optional may be NULL. + */ PTCacheID BKE_ptcache_id_find(struct Object *ob, struct Scene *scene, struct PointCache *cache); void BKE_ptcache_ids_from_object(struct ListBase *lb, struct Object *ob, @@ -294,12 +303,11 @@ void BKE_ptcache_ids_from_object(struct ListBase *lb, /****************** Query funcs ****************************/ -/* Check whether object has a point cache. */ +/** + * Check whether object has a point cache. + */ bool BKE_ptcache_object_has(struct Scene *scene, struct Object *ob, int duplis); -/***************** Global funcs ****************************/ -void BKE_ptcache_remove(void); - /************ ID specific functions ************************/ void BKE_ptcache_id_clear(PTCacheID *id, int mode, unsigned int cfra); bool BKE_ptcache_id_exist(PTCacheID *id, int cfra); @@ -316,23 +324,35 @@ void BKE_ptcache_update_info(PTCacheID *pid); /*********** General cache reading/writing ******************/ -/* Size of cache data type. */ +/** + * Size of cache data type. + */ int BKE_ptcache_data_size(int data_type); -/* Is point with index in memory cache */ +/** + * Is point with index in memory cache? + * Check to see if point number "index" is in `pm` (uses binary search for index data). + */ int BKE_ptcache_mem_index_find(struct PTCacheMem *pm, unsigned int index); /* Memory cache read/write helpers. */ + void BKE_ptcache_mem_pointers_init(struct PTCacheMem *pm, void *cur[BPHYS_TOT_DATA]); void BKE_ptcache_mem_pointers_incr(void *cur[BPHYS_TOT_DATA]); int BKE_ptcache_mem_pointers_seek(int point_index, struct PTCacheMem *pm, void *cur[BPHYS_TOT_DATA]); -/* Main cache reading call. */ +/** + * Main cache reading call. + * Possible to get old or interpolated result. + */ int BKE_ptcache_read(PTCacheID *pid, float cfra, bool no_extrapolate_old); -/* Main cache writing call. */ +/** + * Main cache writing call. + * Writes cache to disk or memory. + */ int BKE_ptcache_write(PTCacheID *pid, unsigned int cfra); /******************* Allocate & free ***************/ @@ -340,41 +360,56 @@ struct PointCache *BKE_ptcache_add(struct ListBase *ptcaches); void BKE_ptcache_free_mem(struct ListBase *mem_cache); void BKE_ptcache_free(struct PointCache *cache); void BKE_ptcache_free_list(struct ListBase *ptcaches); +/* returns first point cache */ struct PointCache *BKE_ptcache_copy_list(struct ListBase *ptcaches_new, const struct ListBase *ptcaches_old, - const int flag); + int flag); /********************** Baking *********************/ -/* Bakes cache with cache_step sized jumps in time, not accurate but very fast. */ +/** + * Bakes cache with cache_step sized jumps in time, not accurate but very fast. + */ void BKE_ptcache_quick_cache_all(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer); -/* Bake cache or simulate to current frame with settings defined in the baker. */ +/** + * Bake cache or simulate to current frame with settings defined in the baker. + * if bake is not given run simulations to current frame. + */ void BKE_ptcache_bake(struct PTCacheBaker *baker); -/* Convert disk cache to memory cache. */ +/** + * Convert disk cache to memory cache. + */ void BKE_ptcache_disk_to_mem(struct PTCacheID *pid); - -/* Convert memory cache to disk cache. */ +/** + * Convert memory cache to disk cache. + */ void BKE_ptcache_mem_to_disk(struct PTCacheID *pid); - -/* Convert disk cache to memory cache and vice versa. Clears the cache that was converted. */ +/** + * Convert disk cache to memory cache and vice versa. Clears the cache that was converted. + */ void BKE_ptcache_toggle_disk_cache(struct PTCacheID *pid); - -/* Rename all disk cache files with a new name. Doesn't touch the actual content of the files. */ +/** + * Rename all disk cache files with a new name. Doesn't touch the actual content of the files. + */ void BKE_ptcache_disk_cache_rename(struct PTCacheID *pid, const char *name_src, const char *name_dst); -/* Loads simulation from external (disk) cache files. */ +/** + * Loads simulation from external (disk) cache files. + */ void BKE_ptcache_load_external(struct PTCacheID *pid); - -/* Set correct flags after successful simulation step */ +/** + * Set correct flags after successful simulation step. + */ void BKE_ptcache_validate(struct PointCache *cache, int framenr); - -/* Set correct flags after unsuccessful simulation step */ +/** + * Set correct flags after unsuccessful simulation step. + */ void BKE_ptcache_invalidate(struct PointCache *cache); /********************** .blend File I/O *********************/ diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index d2d390dc786..d330ea41e6a 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -18,7 +18,7 @@ /** \file * \ingroup bke - * \brief General operations for point-clouds. + * \brief General operations for point clouds. */ #ifdef __cplusplus extern "C" { @@ -38,10 +38,10 @@ extern const char *POINTCLOUD_ATTR_RADIUS; void *BKE_pointcloud_add(struct Main *bmain, const char *name); void *BKE_pointcloud_add_default(struct Main *bmain, const char *name); -struct PointCloud *BKE_pointcloud_new_nomain(const int totpoint); +struct PointCloud *BKE_pointcloud_new_nomain(int totpoint); struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); -void BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); +bool BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); void BKE_pointcloud_update_customdata_pointers(struct PointCloud *pointcloud); bool BKE_pointcloud_customdata_required(struct PointCloud *pointcloud, diff --git a/source/blender/blenkernel/BKE_preferences.h b/source/blender/blenkernel/BKE_preferences.h index bd887c1ea0d..6d6c58e5c1e 100644 --- a/source/blender/blenkernel/BKE_preferences.h +++ b/source/blender/blenkernel/BKE_preferences.h @@ -29,9 +29,16 @@ extern "C" { struct UserDef; struct bUserAssetLibrary; +/** 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); +/** + * Unlink and free a library preference member. + * \note Free's \a library itself. + */ void BKE_preferences_asset_library_remove(struct UserDef *userdef, struct bUserAssetLibrary *library) ATTR_NONNULL(); @@ -39,6 +46,15 @@ void BKE_preferences_asset_library_name_set(struct UserDef *userdef, struct bUserAssetLibrary *library, const char *name) ATTR_NONNULL(); +/** + * Set the library path, ensuring it is pointing to a directory. + * Single blend files can only act as "Current File" library; libraries on disk + * should always be directories. If the path does not exist, that's fine; it can + * created as directory if necessary later. + */ +void BKE_preferences_asset_library_path_set(struct bUserAssetLibrary *library, const char *path) + 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( diff --git a/source/blender/blenkernel/BKE_report.h b/source/blender/blenkernel/BKE_report.h index 5b22918e84c..8b585fd0167 100644 --- a/source/blender/blenkernel/BKE_report.h +++ b/source/blender/blenkernel/BKE_report.h @@ -35,32 +35,37 @@ extern "C" { * These functions also accept NULL in case no error reporting * is needed. */ -/* report structures are stored in DNA */ +/* Report structures are stored in DNA. */ void BKE_reports_init(ReportList *reports, int flag); +/** + * Only frees the list \a reports. + * To make displayed reports disappear, either remove window-manager reports + * (#wmWindowManager.reports, or #CTX_wm_reports()), or use #WM_report_banners_cancel(). + */ 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..68f2319106e 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 */ @@ -38,11 +38,21 @@ struct Object; struct ReportList; struct Scene; -/* -------------- */ -/* Memory Management */ +/* -------------------------------------------------------------------- */ +/** \name Memory Management + * \{ */ +/** + * Free rigid-body world. + */ void BKE_rigidbody_free_world(struct Scene *scene); +/** + * Free rigid-body settings and simulation instances. + */ void BKE_rigidbody_free_object(struct Object *ob, struct RigidBodyWorld *rbw); +/** + * Free rigid-body constraint and simulation instance. + */ void BKE_rigidbody_free_constraint(struct Object *ob); /* ...... */ @@ -50,9 +60,17 @@ void BKE_rigidbody_free_constraint(struct Object *ob); void BKE_rigidbody_object_copy(struct Main *bmain, struct Object *ob_dst, const struct Object *ob_src, - const int flag); + int flag); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Iterator + * \{ */ -/* Callback format for performing operations on ID-pointers for rigidbody world. */ +/** + * Callback format for performing operations on ID-pointers for rigid-body world. + */ typedef void (*RigidbodyWorldIDFunc)(struct RigidBodyWorld *rbw, struct ID **idpoin, void *userdata, @@ -62,43 +80,83 @@ void BKE_rigidbody_world_id_loop(struct RigidBodyWorld *rbw, RigidbodyWorldIDFunc func, void *userdata); -/* -------------- */ -/* Setup */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Setup + * \{ */ -/* create Blender-side settings data - physics objects not initialized yet */ +/** + * Set up RigidBody world. + * + * Create Blender-side settings data - physics objects not initialized yet. + */ struct RigidBodyWorld *BKE_rigidbody_create_world(struct Scene *scene); +/** + * Add rigid body settings to the specified object. + */ struct RigidBodyOb *BKE_rigidbody_create_object(struct Scene *scene, struct Object *ob, short type); +/** + * Add rigid body constraint to the specified object. + */ struct RigidBodyCon *BKE_rigidbody_create_constraint(struct Scene *scene, struct Object *ob, short type); -/* Ensure newly set collections' objects all have required data. */ +/** + * Ensure newly set collections' objects all have required data. + */ void BKE_rigidbody_objects_collection_validate(struct Scene *scene, struct RigidBodyWorld *rbw); void BKE_rigidbody_constraints_collection_validate(struct Scene *scene, struct RigidBodyWorld *rbw); -/* Ensure object added to collection gets RB data if that collection is a RB one. */ +/** + * Ensure object added to collection gets RB data if that collection is a RB one. + */ void BKE_rigidbody_main_collection_object_add(struct Main *bmain, struct Collection *collection, struct Object *object); -/* copy */ -struct RigidBodyWorld *BKE_rigidbody_world_copy(struct RigidBodyWorld *rbw, const int flag); +/** + * Copy. + */ +struct RigidBodyWorld *BKE_rigidbody_world_copy(struct RigidBodyWorld *rbw, int flag); void BKE_rigidbody_world_groups_relink(struct RigidBodyWorld *rbw); -/* 'validate' (i.e. make new or replace old) Physics-Engine objects */ +/** + * 'validate' (i.e. make new or replace old) Physics-Engine objects. + */ +/** + * Create physics sim world given RigidBody world settings + * + * \note this does NOT update object references that the scene uses, + * in case those aren't ready yet! + */ void BKE_rigidbody_validate_sim_world(struct Scene *scene, struct RigidBodyWorld *rbw, bool rebuild); +/** + * Helper function to calculate volume of rigid-body object. + + * TODO: allow a parameter to specify method used to calculate this? + */ void BKE_rigidbody_calc_volume(struct Object *ob, float *r_vol); void BKE_rigidbody_calc_center_of_mass(struct Object *ob, float r_center[3]); -/* -------------- */ -/* Utilities */ +/** \} */ +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +/** + * Get RigidBody world for the given scene, creating one if needed + * + * \param scene: Scene to find active Rigid Body world for. + */ struct RigidBodyWorld *BKE_rigidbody_get_world(struct Scene *scene); bool BKE_rigidbody_add_object(struct Main *bmain, struct Scene *scene, @@ -109,47 +167,74 @@ void BKE_rigidbody_ensure_local_object(struct Main *bmain, struct Object *ob); void BKE_rigidbody_remove_object(struct Main *bmain, struct Scene *scene, struct Object *ob, - const bool free_us); + bool free_us); void BKE_rigidbody_remove_constraint(struct Main *bmain, struct Scene *scene, struct Object *ob, - const bool free_us); + bool free_us); + +/** \} */ -/* -------------- */ -/* Utility Macros */ +/* -------------------------------------------------------------------- */ +/** \name Utility Macros + * \{ */ -/* get mass of Rigid Body Object to supply to RigidBody simulators */ +/** + * Get mass of Rigid Body Object to supply to RigidBody simulators. + */ #define RBO_GET_MASS(rbo) \ (((rbo) && (((rbo)->type == RBO_TYPE_PASSIVE) || ((rbo)->flag & RBO_FLAG_KINEMATIC) || \ ((rbo)->flag & RBO_FLAG_DISABLED))) ? \ (0.0f) : \ ((rbo)->mass)) -/* Get collision margin for Rigid Body Object, triangle mesh and cone shapes cannot embed margin, - * convex hull always uses custom margin. */ +/** + * Get collision margin for Rigid Body Object, triangle mesh and cone shapes cannot embed margin, + * convex hull always uses custom margin. + */ #define RBO_GET_MARGIN(rbo) \ (((rbo)->flag & RBO_FLAG_USE_MARGIN || (rbo)->shape == RB_SHAPE_CONVEXH || \ (rbo)->shape == RB_SHAPE_TRIMESH || (rbo)->shape == RB_SHAPE_CONE) ? \ ((rbo)->margin) : \ (0.04f)) -/* -------------- */ -/* Simulation */ +/** \} */ +/* -------------------------------------------------------------------- */ +/** \name Simulation + * \{ */ + +/** + * Used when canceling transforms - return rigidbody and object to initial states. + */ void BKE_rigidbody_aftertrans_update(struct Object *ob, float loc[3], float rot[3], float quat[4], float rotAxis[3], float rotAngle); +/** + * Sync rigid body and object transformations. + */ void BKE_rigidbody_sync_transforms(struct RigidBodyWorld *rbw, struct Object *ob, float ctime); bool BKE_rigidbody_check_sim_running(struct RigidBodyWorld *rbw, float ctime); bool BKE_rigidbody_is_affected_by_simulation(struct Object *ob); void BKE_rigidbody_cache_reset(struct RigidBodyWorld *rbw); +/** + * Rebuild rigid body world. + * + * NOTE: this needs to be called before frame update to work correctly. + */ void BKE_rigidbody_rebuild_world(struct Depsgraph *depsgraph, struct Scene *scene, float ctime); +/** + * Run RigidBody simulation for the specified physics world. + */ void BKE_rigidbody_do_simulation(struct Depsgraph *depsgraph, struct Scene *scene, float ctime); -/* -------------------- */ -/* Depsgraph evaluation */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Depsgraph evaluation + * \{ */ void BKE_rigidbody_rebuild_sim(struct Depsgraph *depsgraph, struct Scene *scene); @@ -159,6 +244,8 @@ void BKE_rigidbody_object_sync_transforms(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob); +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_scene.h b/source/blender/blenkernel/BKE_scene.h index f3edf8e9f64..a40359e8650 100644 --- a/source/blender/blenkernel/BKE_scene.h +++ b/source/blender/blenkernel/BKE_scene.h @@ -22,6 +22,8 @@ * \ingroup bke */ +#include "BLI_sys_types.h" + #ifdef __cplusplus extern "C" { #endif @@ -46,7 +48,7 @@ typedef enum eSceneCopyMethod { SCE_COPY_FULL = 3, } eSceneCopyMethod; -/* Use as the contents of a 'for' loop: for (SETLOOPER(...)) { ... */ +/** Use as the contents of a 'for' loop: `for (SETLOOPER(...)) { ... }`. */ #define SETLOOPER(_sce_basis, _sce_iter, _base) \ _sce_iter = _sce_basis, \ _base = _setlooper_base_step( \ @@ -64,6 +66,12 @@ typedef enum eSceneCopyMethod { _base; \ _base = _setlooper_base_step(&_sce_iter, NULL, _base) +/** + * Helper function for the #SETLOOPER and #SETLOOPER_VIEW_LAYER macros + * + * It iterates over the bases of the active layer and then the bases + * of the active layer of the background (set) scenes recursively. + */ struct Base *_setlooper_base_step(struct Scene **sce_iter, struct ViewLayer *view_layer, struct Base *base); @@ -75,8 +83,11 @@ struct Scene *BKE_scene_add(struct Main *bmain, const char *name); void BKE_scene_remove_rigidbody_object(struct Main *bmain, struct Scene *scene, struct Object *ob, - const bool free_us); + bool free_us); +/** + * Check if there is any instance of the object in the scene. + */ bool BKE_scene_object_find(struct Scene *scene, struct Object *ob); struct Object *BKE_scene_object_find_by_name(const struct Scene *scene, const char *name); @@ -91,6 +102,10 @@ typedef struct SceneBaseIter { int phase; } SceneBaseIter; +/** + * Used by meta-balls, return *all* objects (including duplis) + * existing in the scene (including scene's sets). + */ int BKE_scene_base_iter_next(struct Depsgraph *depsgraph, struct SceneBaseIter *iter, struct Scene **scene, @@ -99,12 +114,34 @@ int BKE_scene_base_iter_next(struct Depsgraph *depsgraph, struct Object **ob); void BKE_scene_base_flag_to_objects(struct ViewLayer *view_layer); +/** + * Synchronize object base flags + * + * This is usually handled by the depsgraph. + * However, in rare occasions we need to use the latest object flags + * before depsgraph is fully updated. + * + * It should (ideally) only run for copy-on-written objects since this is + * runtime data generated per-view-layer. + */ void BKE_scene_object_base_flag_sync_from_base(struct Base *base); +/** + * Sets the active scene, mainly used when running in background mode + * (`--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. + */ void BKE_scene_set_background(struct Main *bmain, struct Scene *sce); +/** + * Called from `creator_args.c`. + */ struct Scene *BKE_scene_set_name(struct Main *bmain, const char *name); -struct ToolSettings *BKE_toolsettings_copy(struct ToolSettings *toolsettings, const int flag); +/** + * \param flag: copying options (see BKE_lib_id.h's `LIB_ID_COPY_...` flags for more). + */ +struct ToolSettings *BKE_toolsettings_copy(struct ToolSettings *toolsettings, int flag); void BKE_toolsettings_free(struct ToolSettings *toolsettings); struct Scene *BKE_scene_duplicate(struct Main *bmain, struct Scene *sce, eSceneCopyMethod type); @@ -122,23 +159,49 @@ struct Object *BKE_scene_camera_switch_find(struct Scene *scene); /* DURIAN_CAME bool BKE_scene_camera_switch_update(struct Scene *scene); const char *BKE_scene_find_marker_name(const struct Scene *scene, int frame); +/** + * Return the current marker for this frame, + * we can have more than 1 marker per frame, this just returns the first (unfortunately). + */ 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 frame); -/* checks for cycle, returns 1 if it's all OK */ +/** + * Checks for cycle, returns true if it's all OK. + */ bool BKE_scene_validate_setscene(struct Main *bmain, struct Scene *sce); +/** + * Return fractional frame number taking into account sub-frames and time + * remapping. This the time value used by animation, modifiers and physics + * evaluation. */ float BKE_scene_ctime_get(const struct Scene *scene); -float BKE_scene_frame_to_ctime(const struct Scene *scene, const int frame); +/** + * Convert integer frame number to fractional frame number taking into account + * sub-frames and time remapping. + */ +float BKE_scene_frame_to_ctime(const struct Scene *scene, int frame); +/** + * Get current fractional frame based on frame and sub-frame. + */ float BKE_scene_frame_get(const struct Scene *scene); +/** + * Set current frame and sub-frame based on a fractional frame. + */ void BKE_scene_frame_set(struct Scene *scene, float frame); struct TransformOrientationSlot *BKE_scene_orientation_slot_get_from_flag(struct Scene *scene, int flag); struct TransformOrientationSlot *BKE_scene_orientation_slot_get(struct Scene *scene, int slot_index); +/** + * Activate a transform orientation in a 3D view based on an enum value. + * + * \param orientation: If this is #V3D_ORIENT_CUSTOM or greater, the custom transform orientation + * with index \a orientation - #V3D_ORIENT_CUSTOM gets activated. + */ void BKE_scene_orientation_slot_set_index(struct TransformOrientationSlot *orient_slot, int orientation); int BKE_scene_orientation_slot_get_index(const struct TransformOrientationSlot *orient_slot); @@ -154,16 +217,29 @@ void BKE_scene_graph_update_tagged(struct Depsgraph *depsgraph, struct Main *bma void BKE_scene_graph_evaluated_ensure(struct Depsgraph *depsgraph, struct Main *bmain); void BKE_scene_graph_update_for_newframe(struct Depsgraph *depsgraph); -void BKE_scene_graph_update_for_newframe_ex(struct Depsgraph *depsgraph, const bool clear_recalc); +/** + * Applies changes right away, does all sets too. + */ +void BKE_scene_graph_update_for_newframe_ex(struct Depsgraph *depsgraph, bool clear_recalc); +/** + * Ensures given scene/view_layer pair has a valid, up-to-date depsgraph. + * + * \warning Sets matching depsgraph as active, + * so should only be called from the active editing context (usually, from operators). + */ void BKE_scene_view_layer_graph_evaluated_ensure(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer); +/** + * Return default view. + */ struct SceneRenderView *BKE_scene_add_render_view(struct Scene *sce, const char *name); bool BKE_scene_remove_render_view(struct Scene *scene, struct SceneRenderView *srv); -/* render profile */ +/* Render profile. */ + int get_render_subsurf_level(const struct RenderData *r, int lvl, bool for_render); int get_render_child_particle_number(const struct RenderData *r, int num, bool for_render); @@ -174,8 +250,12 @@ 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). */ +/** + * 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). + * + * \note We cannot use `const` as RNA_id_pointer_create is not using a const ID. + */ 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); @@ -191,38 +271,54 @@ int BKE_render_preview_pixel_size(const struct RenderData *r); /**********************************/ -double BKE_scene_unit_scale(const struct UnitSettings *unit, const int unit_type, double value); +/** + * Apply the needed correction factor to value, based on unit_type + * (only length-related are affected currently) and `unit->scale_length`. + */ +double BKE_scene_unit_scale(const struct UnitSettings *unit, int unit_type, double value); + +/* Multi-view. */ -/* multiview */ bool BKE_scene_multiview_is_stereo3d(const struct RenderData *rd); +/** + * Return whether to render this #SceneRenderView. + */ bool BKE_scene_multiview_is_render_view_active(const struct RenderData *rd, const struct SceneRenderView *srv); +/** + * \return true if `viewname` is the first or if the name is NULL or not found. + */ bool BKE_scene_multiview_is_render_view_first(const struct RenderData *rd, const char *viewname); +/** + * \return true if `viewname` is the last or if the name is NULL or not found. + */ bool BKE_scene_multiview_is_render_view_last(const struct RenderData *rd, const char *viewname); int BKE_scene_multiview_num_views_get(const struct RenderData *rd); struct SceneRenderView *BKE_scene_multiview_render_view_findindex(const struct RenderData *rd, - const int view_id); -const char *BKE_scene_multiview_render_view_name_get(const struct RenderData *rd, - const int view_id); + int view_id); +const char *BKE_scene_multiview_render_view_name_get(const struct RenderData *rd, int view_id); int BKE_scene_multiview_view_id_get(const struct RenderData *rd, const char *viewname); void BKE_scene_multiview_filepath_get(struct SceneRenderView *srv, const char *filepath, char *r_filepath); +/** + * When multi-view is not used the `filepath` is as usual (e.g., `Image.jpg`). + * When multi-view 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 + * individual views. + */ void BKE_scene_multiview_view_filepath_get(const struct RenderData *rd, const char *filepath, const char *view, char *r_filepath); const char *BKE_scene_multiview_view_suffix_get(const struct RenderData *rd, const char *viewname); -const char *BKE_scene_multiview_view_id_suffix_get(const struct RenderData *rd, const int view_id); +const char *BKE_scene_multiview_view_id_suffix_get(const struct RenderData *rd, int view_id); void BKE_scene_multiview_view_prefix_get(struct Scene *scene, const char *name, char *r_prefix, const char **r_ext); -void BKE_scene_multiview_videos_dimensions_get(const struct RenderData *rd, - const size_t width, - const size_t height, - size_t *r_width, - size_t *r_height); +void BKE_scene_multiview_videos_dimensions_get( + const struct RenderData *rd, size_t width, size_t height, size_t *r_width, size_t *r_height); int BKE_scene_multiview_num_videos_get(const struct RenderData *rd); /* depsgraph */ @@ -231,10 +327,14 @@ void BKE_scene_ensure_depsgraph_hash(struct Scene *scene); void BKE_scene_free_depsgraph_hash(struct Scene *scene); void BKE_scene_free_view_layer_depsgraph(struct Scene *scene, struct ViewLayer *view_layer); -/* Do not allocate new depsgraph. */ +/** + * \note Do not allocate new depsgraph. + */ struct Depsgraph *BKE_scene_get_depsgraph(const struct Scene *scene, const struct ViewLayer *view_layer); -/* Allocate new depsgraph if necessary. */ +/** + * \note Allocate new depsgraph if necessary. + */ struct Depsgraph *BKE_scene_ensure_depsgraph(struct Main *bmain, struct Scene *scene, struct ViewLayer *view_layer); @@ -245,7 +345,11 @@ void BKE_scene_undo_depsgraphs_restore(struct Main *bmain, struct GHash *depsgra void BKE_scene_transform_orientation_remove(struct Scene *scene, struct TransformOrientation *orientation); struct TransformOrientation *BKE_scene_transform_orientation_find(const struct Scene *scene, - const int index); + int index); +/** + * \return the index that \a orientation has within \a scene's transform-orientation list + * or -1 if not found. + */ int BKE_scene_transform_orientation_get_index(const struct Scene *scene, const struct TransformOrientation *orientation); diff --git a/source/blender/blenkernel/BKE_screen.h b/source/blender/blenkernel/BKE_screen.h index 6f341a12b82..c85ae04a492 100644 --- a/source/blender/blenkernel/BKE_screen.h +++ b/source/blender/blenkernel/BKE_screen.h @@ -38,6 +38,7 @@ struct BlendLibReader; struct BlendWriter; struct Header; struct ID; +struct IDRemapper; struct LibraryForeachIDData; struct ListBase; struct Menu; @@ -117,10 +118,7 @@ typedef struct SpaceType { bContextDataCallback context; /* Used when we want to replace an ID by another (or NULL). */ - void (*id_remap)(struct ScrArea *area, - struct SpaceLink *sl, - struct ID *old_id, - struct ID *new_id); + void (*id_remap)(struct ScrArea *area, struct SpaceLink *sl, const struct IDRemapper *mappings); int (*space_subtype_get)(struct ScrArea *area); void (*space_subtype_set)(struct ScrArea *area, int value); @@ -301,8 +299,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), }; @@ -398,7 +394,8 @@ typedef struct Menu { struct uiLayout *layout; /* runtime for drawing */ } Menu; -/* spacetypes */ +/* Space-types. */ + struct SpaceType *BKE_spacetype_from_id(int spaceid); struct ARegionType *BKE_regiontype_from_id_or_first(const struct SpaceType *st, int regionid); struct ARegionType *BKE_regiontype_from_id(const struct SpaceType *st, int regionid); @@ -407,11 +404,26 @@ void BKE_spacetype_register(struct SpaceType *st); bool BKE_spacetype_exists(int spaceid); void BKE_spacetypes_free(void); /* only for quitting blender */ -/* spacedata */ +/* Space-data. */ + void BKE_spacedata_freelist(ListBase *lb); -void BKE_spacedata_copylist(ListBase *lb1, ListBase *lb2); +/** + * \param lb_dst: should be empty (will be cleared). + */ +void BKE_spacedata_copylist(ListBase *lb_dst, ListBase *lb_src); + +/** + * Facility to set locks for drawing to survive (render) threads accessing drawing data. + * + * \note Lock can become bit-flag too. + * \note Should be replaced in future by better local data handling for threads. + */ void BKE_spacedata_draw_locks(bool set); +/** + * Version of #BKE_area_find_region_type that also works if \a slink + * is not the active space of \a area. + */ struct ARegion *BKE_spacedata_find_region_type(const struct SpaceLink *slink, const struct ScrArea *area, int region_type) ATTR_WARN_UNUSED_RESULT @@ -419,42 +431,68 @@ struct ARegion *BKE_spacedata_find_region_type(const struct SpaceLink *slink, void BKE_spacedata_callback_id_remap_set(void (*func)( struct ScrArea *area, struct SpaceLink *sl, struct ID *old_id, struct ID *new_id)); +/** + * Currently unused! + */ void BKE_spacedata_id_unref(struct ScrArea *area, struct SpaceLink *sl, struct ID *id); -/* area/regions */ +/* Area/regions. */ + struct ARegion *BKE_area_region_copy(const struct SpaceType *st, const struct ARegion *region); +/** + * Doesn't free the region itself. + */ void BKE_area_region_free(struct SpaceType *st, struct ARegion *region); void BKE_area_region_panels_free(struct ListBase *panels); +/** + * Doesn't free the area itself. + */ void BKE_screen_area_free(struct ScrArea *area); -/* Gizmo-maps of a region need to be freed with the region. - * Uses callback to avoid low-level call. */ +/** + * Gizmo-maps of a region need to be freed with the region. + * Uses callback to avoid low-level call. + */ void BKE_region_callback_free_gizmomap_set(void (*callback)(struct wmGizmoMap *)); void BKE_region_callback_refresh_tag_gizmomap_set(void (*callback)(struct wmGizmoMap *)); +/** + * Find a region of type \a region_type in the currently active space of \a area. + * + * \note This does _not_ work if the region to look up is not in the active space. + * Use #BKE_spacedata_find_region_type if that may be the case. + */ struct ARegion *BKE_area_find_region_type(const struct ScrArea *area, int type); struct ARegion *BKE_area_find_region_active_win(struct ScrArea *area); -struct ARegion *BKE_area_find_region_xy(struct ScrArea *area, const int regiontype, int x, int y); +struct ARegion *BKE_area_find_region_xy(struct ScrArea *area, int regiontype, const int xy[2]) + ATTR_NONNULL(3); +/** + * \note This is only for screen level regions (typically menus/popups). + */ struct ARegion *BKE_screen_find_region_xy(struct bScreen *screen, - const int regiontype, - int x, - int y) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1); + int regiontype, + const int xy[2]) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(1, 3); struct ARegion *BKE_screen_find_main_region_at_xy(struct bScreen *screen, - const int space_type, - const int x, - const int y); - + int space_type, + const int xy[2]) ATTR_NONNULL(1, 3); +/** + * \note Ideally we can get the area from the context, + * there are a few places however where this isn't practical. + */ struct ScrArea *BKE_screen_find_area_from_space(struct bScreen *screen, struct SpaceLink *sl) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2); -struct ScrArea *BKE_screen_find_big_area(struct bScreen *screen, - const int spacetype, - const short min); +/** + * \note Using this function is generally a last resort, you really want to be + * using the context when you can - campbell + */ +struct ScrArea *BKE_screen_find_big_area(struct bScreen *screen, int spacetype, short min); struct ScrArea *BKE_screen_area_map_find_area_xy(const struct ScrAreaMap *areamap, - const int spacetype, - int x, - int y); -struct ScrArea *BKE_screen_find_area_xy(struct bScreen *screen, const int spacetype, int x, int y); + int spacetype, + const int xy[2]) ATTR_NONNULL(1, 3); +struct ScrArea *BKE_screen_find_area_xy(struct bScreen *screen, int spacetype, const int xy[2]) + ATTR_NONNULL(1, 3); void BKE_screen_gizmo_tag_refresh(struct bScreen *screen); @@ -464,15 +502,24 @@ bool BKE_screen_is_fullscreen_area(const struct bScreen *screen) ATTR_WARN_UNUSE ATTR_NONNULL(); bool BKE_screen_is_used(const struct bScreen *screen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); -/* zoom factor conversion */ +/* Zoom factor conversion. */ + float BKE_screen_view3d_zoom_to_fac(float camzoom); float BKE_screen_view3d_zoom_from_fac(float zoomfac); void BKE_screen_view3d_shading_init(struct View3DShading *shading); -/* screen */ +/* Screen. */ + +/** + * 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(struct LibraryForeachIDData *data, struct ScrArea *area); +/** + * Free (or release) any data used by this screen (does not free the screen itself). + */ void BKE_screen_free_data(struct bScreen *screen); void BKE_screen_area_map_free(struct ScrAreaMap *area_map) ATTR_NONNULL(); @@ -488,18 +535,28 @@ void BKE_screen_remove_unused_scrverts(struct bScreen *screen); void BKE_screen_header_alignment_reset(struct bScreen *screen); /* .blend file I/O */ + void BKE_screen_view3d_shading_blend_write(struct BlendWriter *writer, struct View3DShading *shading); void BKE_screen_view3d_shading_blend_read_data(struct BlendDataReader *reader, struct View3DShading *shading); void BKE_screen_area_map_blend_write(struct BlendWriter *writer, struct ScrAreaMap *area_map); +/** + * \return false on error. + */ bool BKE_screen_area_map_blend_read_data(struct BlendDataReader *reader, struct ScrAreaMap *area_map); +/** + * And as patch for 2.48 and older. + */ void BKE_screen_view3d_do_versions_250(struct View3D *v3d, ListBase *regions); void BKE_screen_area_blend_read_lib(struct BlendLibReader *reader, struct ID *parent_id, struct ScrArea *area); +/** + * Cannot use #IDTypeInfo callback yet, because of the return value. + */ bool BKE_screen_blend_read_data(struct BlendDataReader *reader, struct bScreen *screen); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_shader_fx.h b/source/blender/blenkernel/BKE_shader_fx.h index 8d1fe709355..432b334a676 100644 --- a/source/blender/blenkernel/BKE_shader_fx.h +++ b/source/blender/blenkernel/BKE_shader_fx.h @@ -149,29 +149,47 @@ typedef struct ShaderFxTypeInfo { #define SHADERFX_TYPE_PANEL_PREFIX "FX_PT_" -/* Initialize global data (type info and some common global storage). */ +/** + * Initialize global data (type info and some common global storage). + */ void BKE_shaderfx_init(void); +/** + * Get an effect's panel type, which was defined in the #panelRegister callback. + * + * \note ShaderFx panel types are assumed to be named with the struct name field concatenated to + * the defined prefix. + */ void BKE_shaderfxType_panel_id(ShaderFxType type, char *r_idname); void BKE_shaderfx_panel_expand(struct ShaderFxData *fx); const ShaderFxTypeInfo *BKE_shaderfx_get_info(ShaderFxType type); struct ShaderFxData *BKE_shaderfx_new(int type); -void BKE_shaderfx_free_ex(struct ShaderFxData *fx, const int flag); +void BKE_shaderfx_free_ex(struct ShaderFxData *fx, int flag); void BKE_shaderfx_free(struct ShaderFxData *fx); +/** + * Check unique name. + */ bool BKE_shaderfx_unique_name(struct ListBase *shaderfx, struct ShaderFxData *fx); bool BKE_shaderfx_depends_ontime(struct ShaderFxData *fx); +/** + * Check whether given shaderfx is not local (i.e. from linked data) when the object is a library + * override. + * + * \param shaderfx: May be NULL, in which case we consider it as a non-local shaderfx case. + */ bool BKE_shaderfx_is_nonlocal_in_liboverride(const struct Object *ob, const struct ShaderFxData *shaderfx); struct ShaderFxData *BKE_shaderfx_findby_type(struct Object *ob, ShaderFxType type); struct ShaderFxData *BKE_shaderfx_findby_name(struct Object *ob, const char *name); void BKE_shaderfx_copydata_generic(const struct ShaderFxData *fx_src, struct ShaderFxData *fx_dst); void BKE_shaderfx_copydata(struct ShaderFxData *fx, struct ShaderFxData *target); -void BKE_shaderfx_copydata_ex(struct ShaderFxData *fx, - struct ShaderFxData *target, - const int flag); +void BKE_shaderfx_copydata_ex(struct ShaderFxData *fx, struct ShaderFxData *target, int flag); void BKE_shaderfx_copy(struct ListBase *dst, const struct ListBase *src); void BKE_shaderfx_foreach_ID_link(struct Object *ob, ShaderFxIDWalkFunc walk, void *userData); +/** + * Check if exist grease pencil effects. + */ bool BKE_shaderfx_has_gpencil(const struct Object *ob); void BKE_shaderfx_blend_write(struct BlendWriter *writer, struct ListBase *fxbase); diff --git a/source/blender/blenkernel/BKE_shrinkwrap.h b/source/blender/blenkernel/BKE_shrinkwrap.h index 70aeb37d995..ea816812344 100644 --- a/source/blender/blenkernel/BKE_shrinkwrap.h +++ b/source/blender/blenkernel/BKE_shrinkwrap.h @@ -34,11 +34,11 @@ extern "C" { * Shrinkwrap is composed by a set of functions and options that define the type of shrink. * * 3 modes are available: - * - Nearest vertex - * - Nearest surface - * - Normal projection + * - Nearest vertex. + * - Nearest surface. + * - Normal projection. * - * ShrinkwrapCalcData encapsulates all needed data for shrinkwrap functions. + * #ShrinkwrapCalcData encapsulates all needed data for shrink-wrap functions. * (So that you don't have to pass an enormous amount of arguments to functions) */ @@ -47,6 +47,7 @@ struct MDeformVert; struct Mesh; struct ModifierEvalContext; struct Object; +struct ShrinkwrapGpencilModifierData; struct ShrinkwrapModifierData; struct SpaceTransform; @@ -74,6 +75,9 @@ typedef struct ShrinkwrapBoundaryData { const ShrinkwrapBoundaryVertData *boundary_verts; } ShrinkwrapBoundaryData; +/** + * Free boundary data for target project. + */ void BKE_shrinkwrap_discard_boundary_data(struct Mesh *mesh); void BKE_shrinkwrap_compute_boundary_data(struct Mesh *mesh); @@ -84,72 +88,104 @@ typedef struct ShrinkwrapTreeData { BVHTree *bvh; BVHTreeFromMesh treeData; - float (*pnors)[3]; + const float (*pnors)[3]; float (*clnors)[3]; ShrinkwrapBoundaryData *boundary; } ShrinkwrapTreeData; -/* Checks if the modifier needs target normals with these settings. */ +/** + * Checks if the modifier needs target normals with these settings. + */ bool BKE_shrinkwrap_needs_normals(int shrinkType, int shrinkMode); -/* Initializes the mesh data structure from the given mesh and settings. */ +/** + * Initializes the mesh data structure from the given mesh and settings. + */ bool BKE_shrinkwrap_init_tree(struct ShrinkwrapTreeData *data, Mesh *mesh, int shrinkType, int shrinkMode, bool force_normals); -/* Frees the tree data if necessary. */ +/** + * Frees the tree data if necessary. + */ void BKE_shrinkwrap_free_tree(struct ShrinkwrapTreeData *data); -/* Implementation of the Shrinkwrap modifier */ +/** + * Main shrink-wrap function (implementation of the shrink-wrap modifier). + */ void shrinkwrapModifier_deform(struct ShrinkwrapModifierData *smd, const struct ModifierEvalContext *ctx, struct Scene *scene, struct Object *ob, struct Mesh *mesh, struct MDeformVert *dvert, - const int defgrp_index, + int defgrp_index, float (*vertexCos)[3], int numVerts); - -/* Used in editmesh_mask_extract.c to shrinkwrap the extracted mesh to the sculpt */ +/* Implementation of the Shrinkwrap Grease Pencil modifier. */ +void shrinkwrapGpencilModifier_deform(struct ShrinkwrapGpencilModifierData *mmd, + struct Object *ob, + struct MDeformVert *dvert, + int defgrp_index, + float (*vertexCos)[3], + int numVerts); + +/** + * Used in `editmesh_mask_extract.c` to shrink-wrap the extracted mesh to the sculpt. + */ void BKE_shrinkwrap_mesh_nearest_surface_deform(struct bContext *C, struct Object *ob_source, struct Object *ob_target); -/* Used in object_remesh.cc 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); -/* - * This function casts a ray in the given BVHTree. - * but it takes into consideration the space_transform, that is: +/** + * This function ray-cast a single vertex and updates the hit if the "hit" is considered valid. * - * if transf was configured with "SPACE_TRANSFORM_SETUP( &transf, ob1, ob2 )" - * then the input (vert, dir, BVHTreeRayHit) must be defined in ob1 coordinates space - * and the BVHTree must be built in ob2 coordinate space. + * \param options: Opts control whether an hit is valid or not. + * Supported options are: + * - #MOD_SHRINKWRAP_CULL_TARGET_FRONTFACE (front faces hits are ignored) + * - #MOD_SHRINKWRAP_CULL_TARGET_BACKFACE (back faces hits are ignored) * + * \param transf: Take into consideration the space_transform, that is: + * if `transf` was configured with `SPACE_TRANSFORM_SETUP( &transf, ob1, ob2)` + * then the input (vert, dir, #BVHTreeRayHit) must be defined in ob1 coordinates space + * and the #BVHTree must be built in ob2 coordinate space. * Thus it provides an easy way to cast the same ray across several trees - * (where each tree was built on its own coords space) + * (where each tree was built on its own coords space). + * + * \return true if "hit" was updated. */ bool BKE_shrinkwrap_project_normal(char options, const float vert[3], const float dir[3], - const float ray_radius, + float ray_radius, const struct SpaceTransform *transf, struct ShrinkwrapTreeData *tree, BVHTreeRayHit *hit); -/* Maps the point to the nearest surface, either by simple nearest, - * or by target normal projection. */ +/** + * Maps the point to the nearest surface, either by simple nearest, or by target normal projection. + */ void BKE_shrinkwrap_find_nearest_surface(struct ShrinkwrapTreeData *tree, struct BVHTreeNearest *nearest, float co[3], int type); -/* Computes a smooth normal of the target (if applicable) at the hit location. */ +/** + * Compute a smooth normal of the target (if applicable) at the hit location. + * + * \param tree: information about the mesh. + * \param transform: transform from the hit coordinate space to the object space; may be null. + * \param r_no: output in hit coordinate space; may be shared with inputs. + */ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, const struct SpaceTransform *transform, int looptri_idx, @@ -157,7 +193,13 @@ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, const float hit_no[3], float r_no[3]); -/* Apply the shrink to surface modes to the given original coordinates and nearest point. */ +/** + * Apply the shrink to surface modes to the given original coordinates and nearest point. + * + * \param tree: mesh data for smooth normals. + * \param transform: transform from the hit coordinate space to the object space; may be null. + * \param r_point_co: may be the same memory location as `point_co`, `hit_co`, or `hit_no`. + */ void BKE_shrinkwrap_snap_point_to_surface(const struct ShrinkwrapTreeData *tree, const struct SpaceTransform *transform, int mode, diff --git a/source/blender/blenkernel/BKE_softbody.h b/source/blender/blenkernel/BKE_softbody.h index 58dc90f62dc..5d010fa2155 100644 --- a/source/blender/blenkernel/BKE_softbody.h +++ b/source/blender/blenkernel/BKE_softbody.h @@ -46,16 +46,24 @@ typedef struct BodyPoint { float springweight; } BodyPoint; -/* allocates and initializes general main data */ +/** + * Allocates and initializes general main data. + */ extern struct SoftBody *sbNew(void); -/* frees internal data and soft-body itself */ +/** + * Frees internal data and soft-body itself. + */ extern void sbFree(struct Object *ob); -/* frees simulation data to reset simulation */ +/** + * Frees simulation data to reset simulation. + */ extern void sbFreeSimulation(struct SoftBody *sb); -/* do one simul step, reading and writing vertex locs from given array */ +/** + * Do one simulation step, reading and writing vertex locs from given array. + * */ extern void sbObjectStep(struct Depsgraph *depsgraph, struct Scene *scene, struct Object *ob, @@ -63,13 +71,30 @@ extern void sbObjectStep(struct Depsgraph *depsgraph, float (*vertexCos)[3], int numVerts); -/* makes totally fresh start situation, resets time */ +/** + * Makes totally fresh start situation, resets time. + */ extern void sbObjectToSoftbody(struct Object *ob); -/* links the soft-body module to a 'test for Interrupt' function */ -/* pass NULL to unlink again */ +/** + * Soft-body global visible functions. + * Links the soft-body module to a 'test for Interrupt' function, pass NULL to clear the callback. + */ extern void sbSetInterruptCallBack(int (*f)(void)); +/** + * A precise position vector denoting the motion of the center of mass give a rotation/scale matrix + * using averaging method, that's why estimate and not calculate see: this is kind of reverse + * engineering: having to states of a point cloud and recover what happened our advantage here we + * know the identity of the vertex there are others methods giving other results. + * + * \param ob: Any object that can do soft-body e.g. mesh, lattice, curve. + * \param lloc: Output of the calculated location (or NULL). + * \param lrot: Output of the calculated rotation (or NULL). + * \param lscale: Output for the calculated scale (or NULL). + * + * For velocity & 2nd order stuff see: #vcloud_estimate_transform_v3. + */ extern void SB_estimate_transform(Object *ob, float lloc[3], float lrot[3][3], float lscale[3][3]); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_spline.hh b/source/blender/blenkernel/BKE_spline.hh index 541ff19c1cd..a87f76da8da 100644 --- a/source/blender/blenkernel/BKE_spline.hh +++ b/source/blender/blenkernel/BKE_spline.hh @@ -24,8 +24,8 @@ #include "FN_generic_virtual_array.hh" -#include "BLI_float3.hh" #include "BLI_float4x4.hh" +#include "BLI_math_vec_types.hh" #include "BLI_vector.hh" #include "BKE_attribute_access.hh" @@ -40,7 +40,8 @@ using SplinePtr = std::unique_ptr<Spline>; /** * A spline is an abstraction of a single branch-less curve section, its evaluation methods, * and data. The spline data itself is just control points and a set of attributes by the set - * of "evaluated" data is often used instead. + * of "evaluated" data is often used instead. Conceptually, the derived vs. original data is + * an essential distinction. Derived data is usually calculated lazily and cached on the spline. * * Any derived class of Spline has to manage two things: * 1. Interpolating arbitrary attribute data from the control points to evaluated points. @@ -106,8 +107,17 @@ class Spline { copy_base_settings(other, *this); } + /** + * Return a new spline with the same data, settings, and attributes. + */ SplinePtr copy() const; + /** + * Return a new spline with the same type and settings like "cyclic", but without any data. + */ SplinePtr copy_only_settings() const; + /** + * The same as #copy, but skips copying dynamic attributes to the new spline. + */ SplinePtr copy_without_attributes() const; static void copy_base_settings(const Spline &src, Spline &dst); @@ -117,9 +127,9 @@ class Spline { virtual int size() const = 0; int segments_size() const; bool is_cyclic() const; - void set_cyclic(const bool value); + void set_cyclic(bool value); - virtual void resize(const int size) = 0; + virtual void resize(int size) = 0; virtual blender::MutableSpan<blender::float3> positions() = 0; virtual blender::Span<blender::float3> positions() const = 0; virtual blender::MutableSpan<float> radii() = 0; @@ -147,11 +157,25 @@ class Spline { virtual blender::Span<blender::float3> evaluated_positions() const = 0; + /** + * 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, and it is necessarily single threaded, so it + * is cached. + */ blender::Span<float> evaluated_lengths() const; + /** + * Return non-owning access to the direction of the curve at each evaluated point. + */ blender::Span<blender::float3> evaluated_tangents() const; + /** + * Return non-owning access to the direction vectors perpendicular to the tangents at every + * evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode. + */ blender::Span<blender::float3> evaluated_normals() const; - void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const; + void bounds_min_max(blender::float3 &min, blender::float3 &max, bool use_evaluated) const; struct LookupResult { /** @@ -172,12 +196,32 @@ class Spline { */ float factor; }; - LookupResult lookup_evaluated_factor(const float factor) const; - LookupResult lookup_evaluated_length(const float length) const; + /** + * Find the position on the evaluated spline at the given portion of the total length. + * The return value is the indices of the two neighboring points at that location and the + * factor between them, which can be used to look up any attribute on the evaluated points. + * \note This does not support extrapolation. + */ + LookupResult lookup_evaluated_factor(float factor) const; + /** + * The same as #lookup_evaluated_factor, but looks up a length directly instead of + * a portion of the total. + */ + LookupResult lookup_evaluated_length(float length) const; - blender::Array<float> sample_uniform_index_factors(const int samples_size) const; - LookupResult lookup_data_from_index_factor(const float index_factor) const; + /** + * Return an array of evenly spaced samples along the length of the spline. The samples are + * indices and factors to the next index encoded in floats. The logic for converting from the + * float values to interpolation data is in #lookup_data_from_index_factor. + */ + blender::Array<float> sample_uniform_index_factors(int samples_size) const; + LookupResult lookup_data_from_index_factor(float index_factor) const; + /** + * Sample any input data with a value for each evaluated point (already interpolated to evaluated + * points) to arbitrary parameters in between the evaluated points. The interpolation is quite + * simple, but this handles the cyclic and end point special cases. + */ void sample_with_index_factors(const blender::fn::GVArray &src, blender::Span<float> index_factors, blender::fn::GMutableSpan dst) const; @@ -187,14 +231,14 @@ class Spline { blender::MutableSpan<T> dst) const { this->sample_with_index_factors( - blender::fn::GVArray_For_VArray(src), index_factors, blender::fn::GMutableSpan(dst)); + blender::fn::GVArray(src), index_factors, blender::fn::GMutableSpan(dst)); } template<typename T> void sample_with_index_factors(blender::Span<T> src, blender::Span<float> index_factors, blender::MutableSpan<T> dst) const { - this->sample_with_index_factors(blender::VArray_For_Span(src), index_factors, dst); + this->sample_with_index_factors(blender::VArray<T>::ForSpan(src), index_factors, dst); } /** @@ -202,13 +246,11 @@ class Spline { * evaluated points. For poly splines, the lifetime of the returned virtual array must not * exceed the lifetime of the input data. */ - virtual blender::fn::GVArrayPtr interpolate_to_evaluated( - const blender::fn::GVArray &src) const = 0; - blender::fn::GVArrayPtr interpolate_to_evaluated(blender::fn::GSpan data) const; - template<typename T> - blender::fn::GVArray_Typed<T> interpolate_to_evaluated(blender::Span<T> data) const + virtual blender::fn::GVArray interpolate_to_evaluated(const blender::fn::GVArray &src) const = 0; + blender::fn::GVArray interpolate_to_evaluated(blender::fn::GSpan data) const; + template<typename T> blender::VArray<T> interpolate_to_evaluated(blender::Span<T> data) const { - return blender::fn::GVArray_Typed<T>(this->interpolate_to_evaluated(blender::fn::GSpan(data))); + return this->interpolate_to_evaluated(blender::fn::GSpan(data)).typed<T>(); } protected: @@ -286,17 +328,9 @@ class BezierSpline final : public Spline { int size() const final; int resolution() const; - void set_resolution(const int value); + void set_resolution(int value); - void add_point(const blender::float3 position, - const HandleType handle_type_left, - const blender::float3 handle_position_left, - const HandleType handle_type_right, - const blender::float3 handle_position_right, - const float radius, - const float tilt); - - void resize(const int size) final; + void resize(int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -306,22 +340,64 @@ class BezierSpline final : public Spline { blender::Span<HandleType> handle_types_left() const; blender::MutableSpan<HandleType> handle_types_left(); blender::Span<blender::float3> handle_positions_left() const; - blender::MutableSpan<blender::float3> handle_positions_left(); + /** + * Get writable access to the handle position. + * + * \param write_only: pass true for an uninitialized spline, this prevents accessing + * uninitialized memory while auto-generating handles. + */ + blender::MutableSpan<blender::float3> handle_positions_left(bool write_only = false); blender::Span<HandleType> handle_types_right() const; blender::MutableSpan<HandleType> handle_types_right(); blender::Span<blender::float3> handle_positions_right() const; - blender::MutableSpan<blender::float3> handle_positions_right(); + /** + * Get writable access to the handle position. + * + * \param write_only: pass true for an uninitialized spline, this prevents accessing + * uninitialized memory while auto-generating handles. + */ + blender::MutableSpan<blender::float3> handle_positions_right(bool write_only = false); + /** + * Recalculate all #Auto and #Vector handles with positions automatically + * derived from the neighboring control points. + */ void ensure_auto_handles() const; void translate(const blender::float3 &translation) override; void transform(const blender::float4x4 &matrix) override; - bool point_is_sharp(const int index) const; + /** + * 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 set_handle_position_right(int index, const blender::float3 &value); + /** + * 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 set_handle_position_left(int index, const blender::float3 &value); + + bool point_is_sharp(int index) const; void mark_cache_invalid() final; int evaluated_points_size() const final; + /** + * Returns access to a cache of offsets into the evaluated point array for each control point. + * While most control point edges generate the number of edges specified by the resolution, + * vector segments only generate one edge. + * + * \note The length of the result is one greater than the number of points, so that the last item + * is the total number of evaluated points. This is useful to avoid recalculating the size of the + * last segment everywhere. + */ blender::Span<int> control_point_offsets() const; + /** + * Returns non-owning access to an array of values containing the information necessary to + * interpolate values from the original control points to evaluated points. The control point + * index is the integer part of each value, and the factor used for interpolating to the next + * control point is the remaining factional part. + */ blender::Span<float> evaluated_mappings() const; blender::Span<blender::float3> evaluated_positions() const final; struct InterpolationData { @@ -333,15 +409,23 @@ class BezierSpline final : public Spline { */ float factor; }; - InterpolationData interpolation_data_from_index_factor(const float index_factor) const; + /** + * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary + * to interpolate data from control points to evaluated points between them. The next control + * point index result will not overflow the size of the control point vectors. + */ + InterpolationData interpolation_data_from_index_factor(float index_factor) const; - virtual blender::fn::GVArrayPtr interpolate_to_evaluated( + virtual blender::fn::GVArray interpolate_to_evaluated( const blender::fn::GVArray &src) const override; - void evaluate_segment(const int index, - const int next_index, + void evaluate_segment(int index, + int next_index, blender::MutableSpan<blender::float3> positions) const; - bool segment_is_vector(const int start_index) const; + /** + * \warning This functional assumes that the spline has more than one point. + */ + bool segment_is_vector(int start_index) const; /** See comment and diagram for #calculate_segment_insertion. */ struct InsertResult { @@ -351,11 +435,34 @@ class BezierSpline final : public Spline { blender::float3 right_handle; blender::float3 handle_next; }; - InsertResult calculate_segment_insertion(const int index, - const int next_index, - const float parameter); + /** + * 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> + */ + InsertResult calculate_segment_insertion(int index, int next_index, float parameter); private: + /** + * If the spline is not cyclic, the direction for the first and last points is just the + * direction formed by the corresponding handles and control points. In the unlikely situation + * that the handles define a zero direction, fallback to using the direction defined by the + * first and last evaluated segments already calculated in #Spline::evaluated_tangents(). + */ void correct_end_tangents() const final; void copy_settings(Spline &dst) const final; void copy_data(Spline &dst) const final; @@ -443,19 +550,14 @@ class NURBSpline final : public Spline { int size() const final; int resolution() const; - void set_resolution(const int value); + void set_resolution(int value); uint8_t order() const; - void set_order(const uint8_t value); - - void add_point(const blender::float3 position, - const float radius, - const float tilt, - const float weight); + void set_order(uint8_t value); bool check_valid_size_and_order() const; int knots_size() const; - void resize(const int size) final; + void resize(int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -472,7 +574,7 @@ class NURBSpline final : public Spline { blender::Span<blender::float3> evaluated_positions() const final; - blender::fn::GVArrayPtr interpolate_to_evaluated(const blender::fn::GVArray &src) const final; + blender::fn::GVArray interpolate_to_evaluated(const blender::fn::GVArray &src) const final; protected: void correct_end_tangents() const final; @@ -485,9 +587,12 @@ class NURBSpline final : public Spline { }; /** - * A Poly spline is like a bezier spline with a resolution of one. The main reason to distinguish + * A Poly spline is like a Bézier spline with a resolution of one. The main reason to distinguish * the two is for reduced complexity and increased performance, since interpolating data to control * points does not change it. + * + * Poly spline code is very simple, since it doesn't do anything that the base #Spline doesn't + * handle. Mostly it just worries about storing the data used by the base class. */ class PolySpline final : public Spline { blender::Vector<blender::float3> positions_; @@ -508,9 +613,7 @@ class PolySpline final : public Spline { int size() const final; - void add_point(const blender::float3 position, const float radius, const float tilt); - - void resize(const int size) final; + void resize(int size) final; blender::MutableSpan<blender::float3> positions() final; blender::Span<blender::float3> positions() const final; blender::MutableSpan<float> radii() final; @@ -523,7 +626,13 @@ class PolySpline final : public Spline { blender::Span<blender::float3> evaluated_positions() const final; - blender::fn::GVArrayPtr interpolate_to_evaluated(const blender::fn::GVArray &src) const final; + /** + * Poly spline interpolation from control points to evaluated points is a special case, since + * the result data is the same as the input data. This function returns a #GVArray that points to + * the original data. Therefore the lifetime of the returned virtual array must not be longer + * than the source data. + */ + blender::fn::GVArray interpolate_to_evaluated(const blender::fn::GVArray &src) const final; protected: void correct_end_tangents() const final; @@ -533,8 +642,12 @@ class PolySpline final : public Spline { }; /** - * A #CurveEval corresponds to the #Curve object data. The name is different for clarity, since - * more of the data is stored in the splines, but also just to be different than the name in DNA. + * A collection of #Spline objects with the same attribute types and names. Most data and + * functionality is in splines, but this contains some helpers for working with them as a group. + * + * \note A #CurveEval corresponds to the #Curve object data. The name is different for clarity, + * since more of the data is stored in the splines, but also just to be different than the name in + * DNA. */ struct CurveEval { private: @@ -553,20 +666,56 @@ struct CurveEval { blender::Span<SplinePtr> splines() const; blender::MutableSpan<SplinePtr> 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 has_spline_with_type(const Spline::Type type) const; - void resize(const int size); + void resize(int size); + /** + * \warning Call #reallocate on the spline's attributes after adding all splines. + */ void add_spline(SplinePtr spline); + void add_splines(blender::MutableSpan<SplinePtr> splines); void remove_splines(blender::IndexMask mask); void translate(const blender::float3 &translation); void transform(const blender::float4x4 &matrix); - void bounds_min_max(blender::float3 &min, blender::float3 &max, const bool use_evaluated) const; + bool bounds_min_max(blender::float3 &min, blender::float3 &max, bool use_evaluated) const; + /** + * 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. + * + * \note The result is one longer than the spline count; the last element is the total size. + */ blender::Array<int> control_point_offsets() const; + /** + * Exactly like #control_point_offsets, but uses the number of evaluated points instead. + */ blender::Array<int> evaluated_point_offsets() const; + /** + * 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> accumulated_spline_lengths() const; + float total_length() const; + int total_control_point_size() const; + + void mark_cache_invalid(); + + /** + * Check the invariants that curve control point attributes should always uphold, necessary + * 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 assert_valid_point_attributes() const; }; diff --git a/source/blender/blenkernel/BKE_studiolight.h b/source/blender/blenkernel/BKE_studiolight.h index 59b1c2b28d9..792186dd260 100644 --- a/source/blender/blenkernel/BKE_studiolight.h +++ b/source/blender/blenkernel/BKE_studiolight.h @@ -143,6 +143,8 @@ typedef struct StudioLight { void *free_function_data; } StudioLight; +/* API */ + void BKE_studiolight_init(void); void BKE_studiolight_free(void); void BKE_studiolight_default(SolidLight lights[4], float light_ambient[3]); @@ -151,12 +153,18 @@ struct StudioLight *BKE_studiolight_findindex(int index, int flag); struct StudioLight *BKE_studiolight_find_default(int flag); void BKE_studiolight_preview(uint *icon_buffer, StudioLight *sl, int icon_id_type); struct ListBase *BKE_studiolight_listbase(void); +/** + * Ensure state of studio-lights. + */ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag); void BKE_studiolight_refresh(void); StudioLight *BKE_studiolight_load(const char *path, int type); StudioLight *BKE_studiolight_create(const char *path, const SolidLight light[4], const float light_ambient[3]); +/** + * Only useful for workbench while editing the user-preferences. + */ StudioLight *BKE_studiolight_studio_edit_get(void); void BKE_studiolight_remove(StudioLight *sl); void BKE_studiolight_set_free_function(StudioLight *sl, diff --git a/source/blender/blenkernel/BKE_subdiv.h b/source/blender/blenkernel/BKE_subdiv.h index 2fb27fad30d..697b3e9ace7 100644 --- a/source/blender/blenkernel/BKE_subdiv.h +++ b/source/blender/blenkernel/BKE_subdiv.h @@ -151,9 +151,9 @@ typedef struct SubdivDisplacement { * Averaging of displacement for vertices created for over coarse vertices * and edges is done by subdiv code. */ void (*eval_displacement)(struct SubdivDisplacement *displacement, - const int ptex_face_index, - const float u, - const float v, + int ptex_face_index, + float u, + float v, const float dPdu[3], const float dPdv[3], float r_D[3]); @@ -188,7 +188,16 @@ typedef struct Subdiv { /* Cached values, are not supposed to be accessed directly. */ struct { /* Indexed by base face index, element indicates total number of ptex - * faces created for preceding base faces. */ + * faces created for preceding base faces. This also stores the final + * ptex offset (the total number of PTex faces) at the end of the array + * so that algorithms can compute the number of ptex faces for a given + * face by computing the delta with the offset for the next face without + * using a separate data structure, e.g.: + * + * const int num_face_ptex_faces = face_ptex_offset[i + 1] - face_ptex_offset[i]; + * + * In total this array has a size of `num base faces + 1`. + */ int *face_ptex_offset; } cache_; } Subdiv; @@ -257,45 +266,48 @@ void BKE_subdiv_displacement_detach(Subdiv *subdiv); /* ============================ TOPOLOGY HELPERS ============================ */ +/* For each element in the array, this stores the total number of ptex faces up to that element, + * with the total number of ptex faces being the last element in the array. The array is of length + * `base face count + 1`. */ int *BKE_subdiv_face_ptex_offset_get(Subdiv *subdiv); /* =========================== PTEX FACES AND GRIDS ========================= */ /* For a given (ptex_u, ptex_v) within a ptex face get corresponding * (grid_u, grid_v) within a grid. */ -BLI_INLINE void BKE_subdiv_ptex_face_uv_to_grid_uv(const float ptex_u, - const float ptex_v, +BLI_INLINE void BKE_subdiv_ptex_face_uv_to_grid_uv(float ptex_u, + float ptex_v, float *r_grid_u, float *r_grid_v); /* Inverse of above. */ -BLI_INLINE void BKE_subdiv_grid_uv_to_ptex_face_uv(const float grid_u, - const float grid_v, +BLI_INLINE void BKE_subdiv_grid_uv_to_ptex_face_uv(float grid_u, + float grid_v, float *r_ptex_u, float *r_ptex_v); /* For a given subdivision level (which is NOT refinement level) get size of * CCG grid (number of grid points on a side). */ -BLI_INLINE int BKE_subdiv_grid_size_from_level(const int level); +BLI_INLINE int BKE_subdiv_grid_size_from_level(int level); /* Simplified version of mdisp_rot_face_to_crn, only handles quad and * works in normalized coordinates. * * NOTE: Output coordinates are in ptex coordinates. */ -BLI_INLINE int BKE_subdiv_rotate_quad_to_corner(const float quad_u, - const float quad_v, +BLI_INLINE int BKE_subdiv_rotate_quad_to_corner(float quad_u, + float quad_v, float *r_corner_u, float *r_corner_v); /* Converts (u, v) coordinate from within a grid to a quad coordinate in * normalized ptex coordinates. */ BLI_INLINE void BKE_subdiv_rotate_grid_to_quad( - const int corner, const float grid_u, const float grid_v, float *r_quad_u, float *r_quad_v); + int corner, float grid_u, float grid_v, float *r_quad_u, float *r_quad_v); /* Convert Blender edge crease value to OpenSubdiv sharpness. */ -BLI_INLINE float BKE_subdiv_edge_crease_to_sharpness_f(float edge_crease); -BLI_INLINE float BKE_subdiv_edge_crease_to_sharpness_char(char edge_crease); +BLI_INLINE float BKE_subdiv_crease_to_sharpness_f(float edge_crease); +BLI_INLINE float BKE_subdiv_crease_to_sharpness_char(char edge_crease); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_subdiv_ccg.h b/source/blender/blenkernel/BKE_subdiv_ccg.h index b7d4ab8d8ed..b3aa966e0d0 100644 --- a/source/blender/blenkernel/BKE_subdiv_ccg.h +++ b/source/blender/blenkernel/BKE_subdiv_ccg.h @@ -46,9 +46,9 @@ struct Subdiv; /* Functor which evaluates mask value at a given (u, v) of given ptex face. */ typedef struct SubdivCCGMaskEvaluator { float (*eval_mask)(struct SubdivCCGMaskEvaluator *mask_evaluator, - const int ptex_face_index, - const float u, - const float v); + int ptex_face_index, + float u, + float v); /* Free the data, not the evaluator itself. */ void (*free)(struct SubdivCCGMaskEvaluator *mask_evaluator); @@ -67,8 +67,7 @@ bool BKE_subdiv_ccg_mask_init_from_paint(SubdivCCGMaskEvaluator *mask_evaluator, /* Functor which evaluates material and flags of a given coarse face. */ typedef struct SubdivCCGMaterialFlagsEvaluator { DMFlagMat (*eval_material_flags)( - struct SubdivCCGMaterialFlagsEvaluator *material_flags_evaluator, - const int coarse_face_index); + struct SubdivCCGMaterialFlagsEvaluator *material_flags_evaluator, int coarse_face_index); /* Free the data, not the evaluator itself. */ void (*free)(struct SubdivCCGMaterialFlagsEvaluator *material_flags_evaluator); @@ -307,10 +306,10 @@ bool BKE_subdiv_ccg_check_coord_valid(const SubdivCCG *subdiv_ccg, const SubdivC * the current vertex are added at the end of the coords array. */ void BKE_subdiv_ccg_neighbor_coords_get(const SubdivCCG *subdiv_ccg, const SubdivCCGCoord *coord, - const bool include_duplicates, + bool include_duplicates, SubdivCCGNeighbors *r_neighbors); -int BKE_subdiv_ccg_grid_to_face_index(const SubdivCCG *subdiv_ccg, const int grid_index); +int BKE_subdiv_ccg_grid_to_face_index(const SubdivCCG *subdiv_ccg, int grid_index); void BKE_subdiv_ccg_eval_limit_point(const SubdivCCG *subdiv_ccg, const SubdivCCGCoord *coord, float r_point[3]); diff --git a/source/blender/blenkernel/BKE_subdiv_eval.h b/source/blender/blenkernel/BKE_subdiv_eval.h index 0b61e62c89c..23bcdcce276 100644 --- a/source/blender/blenkernel/BKE_subdiv_eval.h +++ b/source/blender/blenkernel/BKE_subdiv_eval.h @@ -30,16 +30,26 @@ extern "C" { #endif struct Mesh; +struct OpenSubdiv_EvaluatorCache; struct Subdiv; +typedef enum eSubdivEvaluatorType { + SUBDIV_EVALUATOR_TYPE_CPU, + SUBDIV_EVALUATOR_TYPE_GLSL_COMPUTE, +} eSubdivEvaluatorType; + /* Returns true if evaluator is ready for use. */ -bool BKE_subdiv_eval_begin(struct Subdiv *subdiv); +bool BKE_subdiv_eval_begin(struct Subdiv *subdiv, + eSubdivEvaluatorType evaluator_type, + struct OpenSubdiv_EvaluatorCache *evaluator_cache); /* coarse_vertex_cos is an optional argument which allows to override coordinates of the coarse * mesh. */ bool BKE_subdiv_eval_begin_from_mesh(struct Subdiv *subdiv, const struct Mesh *mesh, - const float (*coarse_vertex_cos)[3]); + const float (*coarse_vertex_cos)[3], + eSubdivEvaluatorType evaluator_type, + struct OpenSubdiv_EvaluatorCache *evaluator_cache); bool BKE_subdiv_eval_refine_from_mesh(struct Subdiv *subdiv, const struct Mesh *mesh, const float (*coarse_vertex_cos)[3]); @@ -55,33 +65,23 @@ void BKE_subdiv_eval_init_displacement(struct Subdiv *subdiv); /* Evaluate point at a limit surface, with optional derivatives and normal. */ void BKE_subdiv_eval_limit_point( - struct Subdiv *subdiv, const int ptex_face_index, const float u, const float v, float r_P[3]); + struct Subdiv *subdiv, int ptex_face_index, float u, float v, float r_P[3]); void BKE_subdiv_eval_limit_point_and_derivatives(struct Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, + int ptex_face_index, + float u, + float v, float r_P[3], float r_dPdu[3], float r_dPdv[3]); -void BKE_subdiv_eval_limit_point_and_normal(struct Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, - float r_P[3], - float r_N[3]); -void BKE_subdiv_eval_limit_point_and_short_normal(struct Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, - float r_P[3], - short r_N[3]); +void BKE_subdiv_eval_limit_point_and_normal( + struct Subdiv *subdiv, int ptex_face_index, float u, float v, float r_P[3], float r_N[3]); /* Evaluate face-varying layer (such as UV). */ void BKE_subdiv_eval_face_varying(struct Subdiv *subdiv, - const int face_varying_channel, - const int ptex_face_index, - const float u, - const float v, + int face_varying_channel, + int ptex_face_index, + float u, + float v, float r_face_varying[2]); /* NOTE: Expects derivatives to be correct. @@ -91,59 +91,16 @@ void BKE_subdiv_eval_face_varying(struct Subdiv *subdiv, * Would be nice to have displacement evaluation function which does not require * knowing derivatives ahead of a time. */ void BKE_subdiv_eval_displacement(struct Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, + int ptex_face_index, + float u, + float v, const float dPdu[3], const float dPdv[3], float r_D[3]); /* Evaluate point on a limit surface with displacement applied to it. */ void BKE_subdiv_eval_final_point( - struct Subdiv *subdiv, const int ptex_face_index, const float u, const float v, float r_P[3]); - -/* Patch queries at given resolution. - * - * Will evaluate patch at uniformly distributed (u, v) coordinates on a grid - * of given resolution, producing resolution^2 evaluation points. The order - * goes as u in rows, v in columns. */ - -void BKE_subdiv_eval_limit_patch_resolution_point(struct Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *buffer, - const int offset, - const int stride); -void BKE_subdiv_eval_limit_patch_resolution_point_and_derivatives(struct Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *du_buffer, - const int du_offset, - const int du_stride, - void *dv_buffer, - const int dv_offset, - const int dv_stride); -void BKE_subdiv_eval_limit_patch_resolution_point_and_normal(struct Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *normal_buffer, - const int normal_offset, - const int normal_stride); -void BKE_subdiv_eval_limit_patch_resolution_point_and_short_normal(struct Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *normal_buffer, - const int normal_offset, - const int normal_stride); + struct Subdiv *subdiv, int ptex_face_index, float u, float v, float r_P[3]); #ifdef __cplusplus } diff --git a/source/blender/blenkernel/BKE_subdiv_foreach.h b/source/blender/blenkernel/BKE_subdiv_foreach.h index 3f74299455d..7d9a589666a 100644 --- a/source/blender/blenkernel/BKE_subdiv_foreach.h +++ b/source/blender/blenkernel/BKE_subdiv_foreach.h @@ -35,76 +35,77 @@ struct SubdivForeachContext; struct SubdivToMeshSettings; typedef bool (*SubdivForeachTopologyInformationCb)(const struct SubdivForeachContext *context, - const int num_vertices, - const int num_edges, - const int num_loops, - const int num_polygons); + int num_vertices, + int num_edges, + int num_loops, + int num_polygons, + const int *subdiv_polygon_offset); typedef void (*SubdivForeachVertexFromCornerCb)(const struct SubdivForeachContext *context, void *tls, - const int ptex_face_index, - const float u, - const float v, - const int coarse_vertex_index, - const int coarse_poly_index, - const int coarse_corner, - const int subdiv_vertex_index); + int ptex_face_index, + float u, + float v, + int coarse_vertex_index, + int coarse_poly_index, + int coarse_corner, + int subdiv_vertex_index); typedef void (*SubdivForeachVertexFromEdgeCb)(const struct SubdivForeachContext *context, void *tls, - const int ptex_face_index, - const float u, - const float v, - const int coarse_edge_index, - const int coarse_poly_index, - const int coarse_corner, - const int subdiv_vertex_index); + int ptex_face_index, + float u, + float v, + int coarse_edge_index, + int coarse_poly_index, + int coarse_corner, + int subdiv_vertex_index); typedef void (*SubdivForeachVertexInnerCb)(const struct SubdivForeachContext *context, void *tls, - const int ptex_face_index, - const float u, - const float v, - const int coarse_poly_index, - const int coarse_corner, - const int subdiv_vertex_index); + int ptex_face_index, + float u, + float v, + int coarse_poly_index, + int coarse_corner, + int subdiv_vertex_index); typedef void (*SubdivForeachEdgeCb)(const struct SubdivForeachContext *context, void *tls, - const int coarse_edge_index, - const int subdiv_edge_index, - const int subdiv_v1, - const int subdiv_v2); + int coarse_edge_index, + int subdiv_edge_index, + int subdiv_v1, + int subdiv_v2); typedef void (*SubdivForeachLoopCb)(const struct SubdivForeachContext *context, void *tls, - const int ptex_face_index, - const float u, - const float v, - const int coarse_loop_index, - const int coarse_poly_index, - const int coarse_corner, - const int subdiv_loop_index, - const int subdiv_vertex_index, - const int subdiv_edge_index); + int ptex_face_index, + float u, + float v, + int coarse_loop_index, + int coarse_poly_index, + int coarse_corner, + int subdiv_loop_index, + int subdiv_vertex_index, + int subdiv_edge_index); typedef void (*SubdivForeachPolygonCb)(const struct SubdivForeachContext *context, void *tls, - const int coarse_poly_index, - const int subdiv_poly_index, - const int start_loop_index, - const int num_loops); + int coarse_poly_index, + int subdiv_poly_index, + int start_loop_index, + int num_loops); typedef void (*SubdivForeachLooseCb)(const struct SubdivForeachContext *context, void *tls, - const int coarse_vertex_index, - const int subdiv_vertex_index); + int coarse_vertex_index, + int subdiv_vertex_index); typedef void (*SubdivForeachVertexOfLooseEdgeCb)(const struct SubdivForeachContext *context, void *tls, - const int coarse_edge_index, - const float u, - const int subdiv_vertex_index); + int coarse_edge_index, + float u, + int subdiv_vertex_index); typedef struct SubdivForeachContext { /* Is called when topology information becomes available. diff --git a/source/blender/blenkernel/BKE_subdiv_modifier.h b/source/blender/blenkernel/BKE_subdiv_modifier.h new file mode 100644 index 00000000000..8a179206dd7 --- /dev/null +++ b/source/blender/blenkernel/BKE_subdiv_modifier.h @@ -0,0 +1,79 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2021 by Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup bke + */ + +#pragma once + +#include "BLI_sys_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Mesh; +struct Object; +struct Scene; +struct Subdiv; +struct SubdivSettings; +struct SubsurfModifierData; + +void BKE_subsurf_modifier_subdiv_settings_init(struct SubdivSettings *settings, + const struct SubsurfModifierData *smd, + bool use_render_params); + +/** + * \param skip_check_is_last: When true, we assume that the modifier passed is the last enabled + * modifier in the stack. + */ +bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const struct Scene *scene, + const struct Object *ob, + const struct SubsurfModifierData *smd, + int required_mode, + bool skip_check_is_last); + +bool BKE_subsurf_modifier_can_do_gpu_subdiv(const struct Scene *scene, + const struct Object *ob, + int required_mode); + +extern void (*BKE_subsurf_modifier_free_gpu_cache_cb)(struct Subdiv *subdiv); + +/** + * Main goal of this function is to give usable subdivision surface descriptor + * which matches settings and topology. + */ +struct Subdiv *BKE_subsurf_modifier_subdiv_descriptor_ensure( + const struct SubsurfModifierData *smd, + const struct SubdivSettings *subdiv_settings, + const struct Mesh *mesh, + bool for_draw_code); + +struct SubsurfRuntimeData *BKE_subsurf_modifier_ensure_runtime(struct SubsurfModifierData *smd); + +/** + * Return the #ModifierMode required for the evaluation of the subsurf modifier, + * which should be used to check if the modifier is enabled. + */ +int BKE_subsurf_modifier_eval_required_mode(bool is_final_render, bool is_edit_mode); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/BKE_subsurf.h b/source/blender/blenkernel/BKE_subsurf.h index 3816a822279..db57076082c 100644 --- a/source/blender/blenkernel/BKE_subsurf.h +++ b/source/blender/blenkernel/BKE_subsurf.h @@ -66,18 +66,30 @@ struct DerivedMesh *subsurf_make_derived_from_derived(struct DerivedMesh *dm, void subsurf_calculate_limit_positions(struct Mesh *me, float (*r_positions)[3]); -/* get gridsize from 'level', level must be greater than zero */ +/** + * Get grid-size from 'level', level must be greater than zero. + */ int BKE_ccg_gridsize(int level); -/* x/y grid coordinates at 'low_level' can be multiplied by the result - * of this function to convert to grid coordinates at 'high_level' */ +/** + * X/Y grid coordinates at 'low_level' can be multiplied by the result + * of this function to convert to grid coordinates at 'high_level'. + */ int BKE_ccg_factor(int low_level, int high_level); +/** + * Translate #GridHidden into the #ME_HIDE flag for MVerts. Assumes + * vertices are in the order output by #ccgDM_copyFinalVertArray. + */ void subsurf_copy_grid_hidden(struct DerivedMesh *dm, const struct MPoly *mpoly, struct MVert *mvert, const struct MDisps *mdisps); +/** + * Translate #GridPaintMask into vertex paint masks. Assumes vertices + * are in the order output by #ccgDM_copyFinalVertArray. + */ void subsurf_copy_grid_paint_mask(struct DerivedMesh *dm, const struct MPoly *mpoly, float *paint_mask, diff --git a/source/blender/blenkernel/BKE_text.h b/source/blender/blenkernel/BKE_text.h index c7120c60020..7415cb90ff2 100644 --- a/source/blender/blenkernel/BKE_text.h +++ b/source/blender/blenkernel/BKE_text.h @@ -30,43 +30,78 @@ struct Main; struct Text; struct TextLine; +/** + * \note caller must handle `compiled` member. + */ void BKE_text_free_lines(struct Text *text); struct Text *BKE_text_add(struct Main *bmain, const char *name); +/** + * Use to a valid UTF-8 sequences. + * this function replaces extended ascii characters. + */ int txt_extended_ascii_as_utf8(char **str); bool BKE_text_reload(struct Text *text); +/** + * Load a text file. + * + * \param is_internal: If \a true, this text data-block only exists in memory, + * not as a file on disk. + * + * \note text data-blocks have no real user but have 'fake user' enabled by default + */ struct Text *BKE_text_load_ex(struct Main *bmain, const char *file, const char *relpath, - const bool is_internal); + bool is_internal); +/** + * Load a text file. + * + * \note Text data-blocks have no user by default, only the 'real user' flag. + */ struct Text *BKE_text_load(struct Main *bmain, const char *file, const char *relpath); void BKE_text_clear(struct Text *text); void BKE_text_write(struct Text *text, const char *str); +/** + * \return codes: + * - 0 if file on disk is the same or Text is in memory only. + * - 1 if file has been modified on disk since last local edit. + * - 2 if file on disk has been deleted. + * - -1 is returned if an error occurs. + */ int BKE_text_file_modified_check(struct Text *text); void BKE_text_file_modified_ignore(struct Text *text); 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); +void txt_order_cursors(struct Text *text, bool reverse); int txt_find_string(struct Text *text, const char *findstr, int wrap, int match_case); 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); -void txt_move_left(struct Text *text, const bool sel); -void txt_move_right(struct Text *text, const bool sel); -void txt_jump_left(struct Text *text, const bool sel, const bool use_init_step); -void txt_jump_right(struct Text *text, const bool sel, const bool use_init_step); -void txt_move_bof(struct Text *text, const bool sel); -void txt_move_eof(struct Text *text, const bool sel); -void txt_move_bol(struct Text *text, const bool sel); -void txt_move_eol(struct Text *text, const bool sel); -void txt_move_toline(struct Text *text, unsigned int line, const bool sel); -void txt_move_to(struct Text *text, unsigned int line, unsigned int ch, const bool sel); +void txt_move_up(struct Text *text, bool sel); +void txt_move_down(struct Text *text, bool sel); +void txt_move_left(struct Text *text, bool sel); +void txt_move_right(struct Text *text, bool sel); +void txt_jump_left(struct Text *text, bool sel, bool use_init_step); +void txt_jump_right(struct Text *text, bool sel, bool use_init_step); +void txt_move_bof(struct Text *text, bool sel); +void txt_move_eof(struct Text *text, bool sel); +void txt_move_bol(struct Text *text, bool sel); +void txt_move_eol(struct Text *text, bool sel); +void txt_move_toline(struct Text *text, unsigned int line, bool sel); +/** + * Moves to a certain byte in a line, not a certain utf8-character. + */ +void txt_move_to(struct Text *text, unsigned int line, unsigned int ch, bool sel); void txt_pop_sel(struct Text *text); void txt_delete_char(struct Text *text); void txt_delete_word(struct Text *text); void txt_delete_selected(struct Text *text); void txt_sel_all(struct Text *text); +/** + * Reverse of #txt_pop_sel + * Clears the selection and ensures the cursor is located + * at the selection (where the cursor is visually while editing). + */ void txt_sel_clear(struct Text *text); void txt_sel_line(struct Text *text); void txt_sel_set(struct Text *text, int startl, int startc, int endl, int endc); @@ -82,7 +117,7 @@ bool txt_unindent(struct Text *text); void txt_comment(struct Text *text); void txt_indent(struct Text *text); bool txt_uncomment(struct Text *text); -void txt_move_lines(struct Text *text, const int direction); +void txt_move_lines(struct Text *text, 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(const struct Text *text); @@ -91,18 +126,20 @@ 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); -/* Utility functions, could be moved somewhere more generic but are python/text related. */ -int text_check_bracket(const char ch); -bool text_check_delim(const char ch); -bool text_check_digit(const char ch); -bool text_check_identifier(const char ch); -bool text_check_identifier_nodigit(const char ch); -bool text_check_whitespace(const char ch); +/** + * Utility functions, could be moved somewhere more generic but are python/text related. + */ +int text_check_bracket(char ch); +bool text_check_delim(char ch); +bool text_check_digit(char ch); +bool text_check_identifier(char ch); +bool text_check_identifier_nodigit(char ch); +bool text_check_whitespace(char ch); int text_find_identifier_start(const char *str, int i); -/* defined in bpy_interface.c */ -extern int text_check_identifier_unicode(const unsigned int ch); -extern int text_check_identifier_nodigit_unicode(const unsigned int ch); +/* EVIL: defined in `bpy_interface.c`. */ +extern int text_check_identifier_unicode(unsigned int ch); +extern int text_check_identifier_nodigit_unicode(unsigned int ch); enum { TXT_MOVE_LINE_UP = -1, @@ -110,7 +147,14 @@ enum { }; /* Fast non-validating buffer conversion for undo. */ + +/** + * Create a buffer, the only requirement is #txt_from_buf_for_undo can decode it. + */ char *txt_to_buf_for_undo(struct Text *text, int *r_buf_len); +/** + * Decode a buffer from #txt_to_buf_for_undo. + */ void txt_from_buf_for_undo(struct Text *text, const char *buf, int buf_len); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_text_suggestions.h b/source/blender/blenkernel/BKE_text_suggestions.h index 7561e1d1d08..482141c1032 100644 --- a/source/blender/blenkernel/BKE_text_suggestions.h +++ b/source/blender/blenkernel/BKE_text_suggestions.h @@ -66,7 +66,7 @@ short texttool_text_is_active(struct Text *text); /* Suggestions */ void texttool_suggest_add(const char *name, char type); -void texttool_suggest_prefix(const char *prefix, const int prefix_len); +void texttool_suggest_prefix(const char *prefix, int prefix_len); void texttool_suggest_clear(void); SuggItem *texttool_suggest_first(void); SuggItem *texttool_suggest_last(void); diff --git a/source/blender/blenkernel/BKE_texture.h b/source/blender/blenkernel/BKE_texture.h index ef322d0cd31..2683ab00fa4 100644 --- a/source/blender/blenkernel/BKE_texture.h +++ b/source/blender/blenkernel/BKE_texture.h @@ -42,6 +42,9 @@ struct TexResult; /** #ColorBand.data length. */ #define MAXCOLORBAND 32 +/** + * Utility for all IDs using those texture slots. + */ void BKE_texture_mtex_foreach_id(struct LibraryForeachIDData *data, struct MTex *mtex); void BKE_texture_default(struct Tex *tex); @@ -50,6 +53,9 @@ void BKE_texture_type_set(struct Tex *tex, int type); void BKE_texture_mtex_default(struct MTex *mtex); struct MTex *BKE_texture_mtex_add(void); +/** + * Slot -1 for first free ID. + */ struct MTex *BKE_texture_mtex_add_id(struct ID *id, int slot); /* UNUSED */ // void autotexname(struct Tex *tex); @@ -76,9 +82,12 @@ void BKE_texture_pointdensity_init_data(struct PointDensity *pd); void BKE_texture_pointdensity_free_data(struct PointDensity *pd); void BKE_texture_pointdensity_free(struct PointDensity *pd); struct PointDensity *BKE_texture_pointdensity_add(void); -struct PointDensity *BKE_texture_pointdensity_copy(const struct PointDensity *pd, const int flag); +struct PointDensity *BKE_texture_pointdensity_copy(const struct PointDensity *pd, int flag); bool BKE_texture_dependsOnTime(const struct Tex *texture); +/** + * \returns true if this texture can use its #Texture.ima (even if its NULL). + */ bool BKE_texture_is_image_user(const struct Tex *tex); void BKE_texture_get_value_ex(const struct Scene *scene, @@ -94,6 +103,9 @@ void BKE_texture_get_value(const struct Scene *scene, struct TexResult *texres, bool use_color_management); +/** + * Make sure all images used by texture are loaded into pool. + */ void BKE_texture_fetch_images_for_pool(struct Tex *texture, struct ImagePool *pool); #ifdef __cplusplus diff --git a/source/blender/blenkernel/BKE_tracking.h b/source/blender/blenkernel/BKE_tracking.h index 47145a7d6bd..7769215d616 100644 --- a/source/blender/blenkernel/BKE_tracking.h +++ b/source/blender/blenkernel/BKE_tracking.h @@ -46,20 +46,53 @@ struct rcti; /* **** Common functions **** */ +/** + * Free tracking structure, only frees structure contents + * (if structure is allocated in heap, it shall be handled outside). + * + * All the pointers inside structure becomes invalid after this call. + */ void BKE_tracking_free(struct MovieTracking *tracking); +/** + * Copy tracking structure content. + */ void BKE_tracking_copy(struct MovieTracking *tracking_dst, const struct MovieTracking *tracking_src, - const int flag); + int flag); +/** + * Initialize motion tracking settings to default values, + * used when new movie clip data-block is created. + */ void BKE_tracking_settings_init(struct MovieTracking *tracking); +/** + * Get list base of active object's tracks. + */ struct ListBase *BKE_tracking_get_active_tracks(struct MovieTracking *tracking); +/** + * Get list base of active object's plane tracks. + */ struct ListBase *BKE_tracking_get_active_plane_tracks(struct MovieTracking *tracking); +/** + * Get reconstruction data of active object. + */ struct MovieTrackingReconstruction *BKE_tracking_get_active_reconstruction( struct MovieTracking *tracking); -/* matrices for constraints and drawing */ +/* Matrices for constraints and drawing. */ + +/** + * Get transformation matrix for a given object which is used + * for parenting motion tracker reconstruction to 3D world. + */ void BKE_tracking_get_camera_object_matrix(struct Object *camera_object, float mat[4][4]); +/** + * Get projection matrix for camera specified by given tracking object + * and frame number. + * + * \note frame number should be in clip space, not scene space. + */ void BKE_tracking_get_projection_matrix(struct MovieTracking *tracking, struct MovieTrackingObject *object, int framenr, @@ -68,16 +101,45 @@ void BKE_tracking_get_projection_matrix(struct MovieTracking *tracking, float mat[4][4]); /* **** Clipboard **** */ +/** + * Free clipboard by freeing memory used by all tracks in it. + */ void BKE_tracking_clipboard_free(void); +/** + * Copy selected tracks from specified object to the clipboard. + */ void BKE_tracking_clipboard_copy_tracks(struct MovieTracking *tracking, struct MovieTrackingObject *object); +/** + * Check whether there are any tracks in the clipboard. + */ bool BKE_tracking_clipboard_has_tracks(void); +/** + * Paste tracks from clipboard to specified object. + * + * Names of new tracks in object are guaranteed to be unique here. + */ void BKE_tracking_clipboard_paste_tracks(struct MovieTracking *tracking, struct MovieTrackingObject *object); /* **** Track **** */ + +/** + * Add new empty track to the given list of tracks. + * + * It is required that caller will append at least one marker to avoid degenerate tracks. + */ struct MovieTrackingTrack *BKE_tracking_track_add_empty(struct MovieTracking *tracking, struct ListBase *tracks_list); +/** + * Add new track to a specified tracks base. + * + * Coordinates are expected to be in normalized 0..1 space, + * frame number is expected to be in clip space. + * + * Width and height are clip's dimension used to scale track's + * pattern and search regions. + */ struct MovieTrackingTrack *BKE_tracking_track_add(struct MovieTracking *tracking, struct ListBase *tracksbase, float x, @@ -85,33 +147,87 @@ struct MovieTrackingTrack *BKE_tracking_track_add(struct MovieTracking *tracking int framenr, int width, int height); +/** + * Duplicate the specified track, result will no belong to any list. + */ struct MovieTrackingTrack *BKE_tracking_track_duplicate(struct MovieTrackingTrack *track); +/** + * Ensure specified track has got unique name, + * if it's not name of specified track will be changed + * keeping names of all other tracks unchanged. + */ void BKE_tracking_track_unique_name(struct ListBase *tracksbase, struct MovieTrackingTrack *track); +/** + * Free specified track, only frees contents of a structure + * (if track is allocated in heap, it shall be handled outside). + * + * All the pointers inside track becomes invalid after this call. + */ void BKE_tracking_track_free(struct MovieTrackingTrack *track); +/** + * Get frame numbers of the very first and last markers. + * There is no check on whether the marker is enabled or not. + */ void BKE_tracking_track_first_last_frame_get(const struct MovieTrackingTrack *track, int *r_first_frame, int *r_last_frame); +/** + * Find the minimum starting frame and maximum ending frame within given set of tracks. + */ void BKE_tracking_tracks_first_last_frame_minmax(/*const*/ struct MovieTrackingTrack **tracks, - const int num_tracks, + int num_tracks, int *r_first_frame, int *r_last_frame); int BKE_tracking_count_selected_tracks_in_list(const struct ListBase *tracks_list); int BKE_tracking_count_selected_tracks_in_active_object(/*const*/ struct MovieTracking *tracking); -/* Get array of selected tracks from the current active object in the tracking structure. - * If nothing is selected then the result is nullptr and `r_num_tracks` is set to 0. */ +/** + * Get array of selected tracks from the current active object in the tracking structure. + * If nothing is selected then the result is nullptr and `r_num_tracks` is set to 0. + */ struct MovieTrackingTrack **BKE_tracking_selected_tracks_in_active_object( struct MovieTracking *tracking, int *r_num_tracks); +/** + * Set flag for all specified track's areas. + * + * \param area: which part of marker should be selected. see TRACK_AREA_* constants. + * \param flag: flag to be set for areas. + */ void BKE_tracking_track_flag_set(struct MovieTrackingTrack *track, int area, int flag); +/** + * Clear flag from all specified track's areas. + * + * \param area: which part of marker should be selected. see TRACK_AREA_* constants. + * \param flag: flag to be cleared for areas. + */ void BKE_tracking_track_flag_clear(struct MovieTrackingTrack *track, int area, int flag); +/** + * Check whether track has got marker at specified frame. + * + * \note frame number should be in clip space, not scene space. + */ bool BKE_tracking_track_has_marker_at_frame(struct MovieTrackingTrack *track, int framenr); +/** + * Check whether track has got enabled marker at specified frame. + * + * \note frame number should be in clip space, not scene space. + */ bool BKE_tracking_track_has_enabled_marker_at_frame(struct MovieTrackingTrack *track, int framenr); +/** + * Clear track's path: + * + * - If action is #TRACK_CLEAR_REMAINED path from `ref_frame+1` up to end will be clear. + * - If action is #TRACK_CLEAR_UPTO path from the beginning up to `ref_frame-1` will be clear. + * - If action is #TRACK_CLEAR_ALL only marker at frame ref_frame will remain. + * + * \note frame number should be in clip space, not scene space. + */ void BKE_tracking_track_path_clear(struct MovieTrackingTrack *track, int ref_frame, int action); void BKE_tracking_tracks_join(struct MovieTracking *tracking, @@ -120,7 +236,7 @@ void BKE_tracking_tracks_join(struct MovieTracking *tracking, void BKE_tracking_tracks_average(struct MovieTrackingTrack *dst_track, /*const*/ struct MovieTrackingTrack **src_tracks, - const int num_src_tracks); + int num_src_tracks); struct MovieTrackingTrack *BKE_tracking_track_get_named(struct MovieTracking *tracking, struct MovieTrackingObject *object, @@ -140,7 +256,11 @@ float BKE_tracking_track_get_weight_for_marker(struct MovieClip *clip, struct MovieTrackingTrack *track, struct MovieTrackingMarker *marker); -/* selection */ +/* Selection */ + +/** + * \param area: which part of marker should be selected. see TRACK_AREA_* constants. + */ void BKE_tracking_track_select(struct ListBase *tracksbase, struct MovieTrackingTrack *track, int area, @@ -155,21 +275,33 @@ void BKE_tracking_marker_delete(struct MovieTrackingTrack *track, int framenr); void BKE_tracking_marker_clamp(struct MovieTrackingMarker *marker, int event); +/** + * Get marker closest to the given frame number. + * + * If there is maker with exact frame number it returned. + * Otherwise, marker with highest frame number but lower than the requested + * frame is returned if such marker exists. Otherwise, the marker with lowest + * frame number greater than the requested frame number is returned. + * + * This function has complexity of `O(log number_of_markers)`. + */ struct MovieTrackingMarker *BKE_tracking_marker_get(struct MovieTrackingTrack *track, int framenr); struct MovieTrackingMarker *BKE_tracking_marker_get_exact(struct MovieTrackingTrack *track, int framenr); struct MovieTrackingMarker *BKE_tracking_marker_ensure(struct MovieTrackingTrack *track, int framenr); -/* Get marker position, possibly interpolating gap between key-framed/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 * involved. * - * Returns truth if the result is usable. */ + * \returns truth if the result is usable. + */ bool BKE_tracking_marker_get_interpolated(struct MovieTrackingTrack *track, - const int framenr, + int framenr, struct MovieTrackingMarker *r_marker); void BKE_tracking_marker_pattern_minmax(const struct MovieTrackingMarker *marker, @@ -181,12 +313,21 @@ void BKE_tracking_marker_get_subframe_position(struct MovieTrackingTrack *track, float pos[2]); /* **** Plane Track **** */ +/** + * Creates new plane track out of selected point tracks. + */ struct MovieTrackingPlaneTrack *BKE_tracking_plane_track_add(struct MovieTracking *tracking, struct ListBase *plane_tracks_base, struct ListBase *tracks, int framenr); void BKE_tracking_plane_track_unique_name(struct ListBase *plane_tracks_base, struct MovieTrackingPlaneTrack *plane_track); +/** + * Free specified plane track, only frees contents of a structure + * (if track is allocated in heap, it shall be handled outside). + * + * All the pointers inside track becomes invalid after this call. + */ void BKE_tracking_plane_track_free(struct MovieTrackingPlaneTrack *plane_track); bool BKE_tracking_plane_track_has_marker_at_frame(struct MovieTrackingPlaneTrack *plane_track, @@ -222,10 +363,21 @@ struct MovieTrackingPlaneMarker *BKE_tracking_plane_marker_insert( struct MovieTrackingPlaneTrack *plane_track, struct MovieTrackingPlaneMarker *plane_marker); void BKE_tracking_plane_marker_delete(struct MovieTrackingPlaneTrack *plane_track, int framenr); +/** + * Get a plane marker at given frame, + * If there's no such marker, closest one from the left side will be returned. + */ struct MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get( struct MovieTrackingPlaneTrack *plane_track, int framenr); +/** + * Get a plane marker at exact given frame, if there's no marker at the frame, + * NULL will be returned. + */ struct MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get_exact( struct MovieTrackingPlaneTrack *plane_track, int framenr); +/** + * Ensure there's a marker for the given frame. + */ struct MovieTrackingPlaneMarker *BKE_tracking_plane_marker_ensure( struct MovieTrackingPlaneTrack *plane_track, int framenr); void BKE_tracking_plane_marker_get_subframe_corners(struct MovieTrackingPlaneTrack *plane_track, @@ -255,6 +407,9 @@ struct MovieTrackingReconstruction *BKE_tracking_object_get_reconstruction( struct MovieTracking *tracking, struct MovieTrackingObject *object); /* **** Camera **** */ +/** + * Converts principal offset from center to offset of blender's camera. + */ void BKE_tracking_camera_shift_get( struct MovieTracking *tracking, int winx, int winy, float *shiftx, float *shifty); void BKE_tracking_camera_to_blender(struct MovieTracking *tracking, @@ -346,10 +501,21 @@ struct ImBuf *BKE_tracking_get_search_imbuf(struct ImBuf *ibuf, bool anchored, bool disable_channels); +/** + * Zap channels from the imbuf that are disabled by the user. this can lead to + * better tracks sometimes. however, instead of simply zeroing the channels + * out, do a partial gray-scale conversion so the display is better. + */ void BKE_tracking_disable_channels( struct ImBuf *ibuf, bool disable_red, bool disable_green, bool disable_blue, bool grayscale); /* **** 2D tracking **** */ + +/** + * Refine marker's position using previously known keyframe. + * Direction of searching for a keyframe depends on backwards flag, + * which means if backwards is false, previous keyframe will be as reference. + */ void BKE_tracking_refine_marker(struct MovieClip *clip, struct MovieTrackingTrack *track, struct MovieTrackingMarker *marker, @@ -359,7 +525,7 @@ void BKE_tracking_refine_marker(struct MovieClip *clip, struct AutoTrackContext *BKE_autotrack_context_new(struct MovieClip *clip, struct MovieClipUser *user, - const bool is_backwards); + bool is_backwards); bool BKE_autotrack_context_step(struct AutoTrackContext *context); void BKE_autotrack_context_sync(struct AutoTrackContext *context); void BKE_autotrack_context_sync_user(struct AutoTrackContext *context, struct MovieClipUser *user); @@ -368,6 +534,9 @@ void BKE_autotrack_context_free(struct AutoTrackContext *context); /* **** Plane tracking **** */ +/** + * \note frame number should be in clip space, not scene space. + */ void BKE_tracking_track_plane_from_existing_motion(struct MovieTrackingPlaneTrack *plane_track, int start_frame); void BKE_tracking_retrack_plane_from_existing_motion_at_segment( @@ -377,11 +546,20 @@ void BKE_tracking_homography_between_two_quads(/*const*/ float reference_corners float H[3][3]); /* **** Camera solving **** */ + +/** + * Perform early check on whether everything is fine to start reconstruction. + */ bool BKE_tracking_reconstruction_check(struct MovieTracking *tracking, struct MovieTrackingObject *object, char *error_msg, int error_size); +/** + * Create context for camera/object motion reconstruction. + * Copies all data needed for reconstruction from movie clip datablock, + * so editing this clip is safe during reconstruction job is in progress. + */ struct MovieReconstructContext *BKE_tracking_reconstruction_context_new( struct MovieClip *clip, struct MovieTrackingObject *object, @@ -389,13 +567,29 @@ struct MovieReconstructContext *BKE_tracking_reconstruction_context_new( int keyframe2, int width, int height); +/** + * Free memory used by a reconstruction process. + */ void BKE_tracking_reconstruction_context_free(struct MovieReconstructContext *context); +/** + * Solve camera/object motion and reconstruct 3D markers position + * from a prepared reconstruction context. + * + * stop is not actually used at this moment, so reconstruction + * job could not be stopped. + * + * do_update, progress and stat_message are set by reconstruction + * callback in libmv side and passing to an interface. + */ void BKE_tracking_reconstruction_solve(struct MovieReconstructContext *context, short *stop, short *do_update, float *progress, char *stats_message, int message_size); +/** + * Finish reconstruction process by copying reconstructed data to an actual movie clip data-block. + */ bool BKE_tracking_reconstruction_finish(struct MovieReconstructContext *context, struct MovieTracking *tracking); @@ -405,9 +599,16 @@ void BKE_tracking_reconstruction_report_error_message(struct MovieReconstructCon const char *BKE_tracking_reconstruction_error_message_get( const struct MovieReconstructContext *context); +/** + * Apply scale on all reconstructed cameras and bundles, used by camera scale apply operator. + */ void BKE_tracking_reconstruction_scale(struct MovieTracking *tracking, float scale[3]); /* **** Feature detection **** */ + +/** + * Detect features using FAST detector. + */ void BKE_tracking_detect_fast(struct MovieTracking *tracking, struct ListBase *tracksbase, struct ImBuf *ibuf, @@ -418,6 +619,9 @@ void BKE_tracking_detect_fast(struct MovieTracking *tracking, struct bGPDlayer *layer, bool place_outside_layer); +/** + * Detect features using Harris detector. + */ void BKE_tracking_detect_harris(struct MovieTracking *tracking, struct ListBase *tracksbase, struct ImBuf *ibuf, @@ -429,6 +633,24 @@ void BKE_tracking_detect_harris(struct MovieTracking *tracking, bool place_outside_layer); /* **** 2D stabilization **** */ + +/** + * Get stabilization data (translation, scaling and angle) for a given frame. + * Returned data describes how to compensate the detected movement, but with any + * chosen scale factor already applied and any target frame position already compensated. + * In case stabilization fails or is disabled, neutral values are returned. + * + * \param framenr: is a frame number, relative to the clip (not relative to the scene timeline). + * \param width: is an effective width of the canvas (square pixels), used to scale the + * determined translation. + * + * Outputs: + * \param translation: of the lateral shift, absolute canvas coordinates (square pixels). + * \param scale: of the scaling to apply. + * \param angle: of the rotation angle, relative to the frame center. + * + * TODO(sergey): Use `r_` prefix for output parameters here. + */ void BKE_tracking_stabilization_data_get(struct MovieClip *clip, int framenr, int width, @@ -436,12 +658,30 @@ void BKE_tracking_stabilization_data_get(struct MovieClip *clip, float translation[2], float *scale, float *angle); +/** + * Stabilize given image buffer using stabilization data for a specified frame number. + * + * \note frame number should be in clip space, not scene space. + * + * TODO(sergey): Use `r_` prefix for output parameters here. + */ struct ImBuf *BKE_tracking_stabilize_frame(struct MovieClip *clip, int framenr, struct ImBuf *ibuf, float translation[2], float *scale, float *angle); +/** + * Build a 4x4 transformation matrix based on the given 2D stabilization data. + * mat is a 4x4 matrix in homogeneous coordinates, adapted to the + * final image buffer size and compensated for pixel aspect ratio, + * ready for direct OpenGL drawing. + * + * TODO(sergey): The signature of this function should be changed. we actually + * don't need the dimensions of the image buffer. Instead we + * should consider to provide the pivot point of the rotation as a + * further stabilization data parameter. + */ void BKE_tracking_stabilization_data_to_mat4(int width, int height, float aspect, @@ -450,17 +690,30 @@ void BKE_tracking_stabilization_data_to_mat4(int width, float angle, float mat[4][4]); -/* Dopesheet */ +/* Dope-sheet */ + +/** + * Tag dope-sheet for update, actual update will happen later when it'll be actually needed. + */ void BKE_tracking_dopesheet_tag_update(struct MovieTracking *tracking); +/** + * Do dope-sheet update, if update is not needed nothing will happen. + */ void BKE_tracking_dopesheet_update(struct MovieTracking *tracking); /* **** Query/search **** */ +/** + * \note Returns NULL if the track comes from camera object,. + */ struct MovieTrackingObject *BKE_tracking_find_object_for_track( const struct MovieTracking *tracking, const struct MovieTrackingTrack *track); struct ListBase *BKE_tracking_find_tracks_list_for_track(struct MovieTracking *tracking, const struct MovieTrackingTrack *track); +/** + * \note Returns NULL if the track comes from camera object,. + */ struct MovieTrackingObject *BKE_tracking_find_object_for_plane_track( const struct MovieTracking *tracking, const struct MovieTrackingPlaneTrack *plane_track); struct ListBase *BKE_tracking_find_tracks_list_for_plane_track( diff --git a/source/blender/blenkernel/BKE_type_conversions.hh b/source/blender/blenkernel/BKE_type_conversions.hh new file mode 100644 index 00000000000..ebfb13cd08f --- /dev/null +++ b/source/blender/blenkernel/BKE_type_conversions.hh @@ -0,0 +1,84 @@ +/* + * 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 "FN_multi_function.hh" + +namespace blender::bke { + +using fn::CPPType; + +struct ConversionFunctions { + const fn::MultiFunction *multi_function; + void (*convert_single_to_initialized)(const void *src, void *dst); + void (*convert_single_to_uninitialized)(const void *src, void *dst); +}; + +class DataTypeConversions { + private: + Map<std::pair<fn::MFDataType, fn::MFDataType>, ConversionFunctions> conversions_; + + public: + void add(fn::MFDataType from_type, + fn::MFDataType to_type, + const fn::MultiFunction &fn, + void (*convert_single_to_initialized)(const void *src, void *dst), + void (*convert_single_to_uninitialized)(const void *src, void *dst)) + { + conversions_.add_new({from_type, to_type}, + {&fn, convert_single_to_initialized, convert_single_to_uninitialized}); + } + + const ConversionFunctions *get_conversion_functions(fn::MFDataType from, fn::MFDataType to) const + { + return conversions_.lookup_ptr({from, to}); + } + + const ConversionFunctions *get_conversion_functions(const CPPType &from, const CPPType &to) const + { + return this->get_conversion_functions(fn::MFDataType::ForSingle(from), + fn::MFDataType::ForSingle(to)); + } + + const fn::MultiFunction *get_conversion_multi_function(fn::MFDataType from, + fn::MFDataType to) const + { + const ConversionFunctions *functions = this->get_conversion_functions(from, to); + return functions ? functions->multi_function : nullptr; + } + + bool is_convertible(const CPPType &from_type, const CPPType &to_type) const + { + return conversions_.contains( + {fn::MFDataType::ForSingle(from_type), fn::MFDataType::ForSingle(to_type)}); + } + + void convert_to_uninitialized(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const; + + void convert_to_initialized_n(fn::GSpan from_span, fn::GMutableSpan to_span) const; + + fn::GVArray try_convert(fn::GVArray varray, const CPPType &to_type) const; + + fn::GVMutableArray try_convert(fn::GVMutableArray varray, const CPPType &to_type) const; +}; + +const DataTypeConversions &get_implicit_type_conversions(); + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_undo_system.h b/source/blender/blenkernel/BKE_undo_system.h index 2973a432723..88a9ac9d0bf 100644 --- a/source/blender/blenkernel/BKE_undo_system.h +++ b/source/blender/blenkernel/BKE_undo_system.h @@ -91,7 +91,7 @@ typedef struct UndoStep { /** When this is true, undo/memfile read code is allowed to re-use old data-blocks for unchanged * IDs, and existing depsgraphes. This has to be forbidden in some cases (like renamed IDs). */ bool use_old_bmain_data; - /** For use by undo systems that accumulate changes (text editor, painting). */ + /** For use by undo systems that accumulate changes (mesh-sculpt & image-painting). */ bool is_applied; /* Over alloc 'type->struct_size'. */ } UndoStep; @@ -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); @@ -133,7 +133,7 @@ typedef struct UndoType { bool (*step_encode)(struct bContext *C, struct Main *bmain, UndoStep *us); void (*step_decode)( - struct bContext *C, struct Main *bmain, UndoStep *us, const eUndoStepDir dir, bool is_final); + struct bContext *C, struct Main *bmain, UndoStep *us, eUndoStepDir dir, bool is_final); /** * \note When freeing all steps, @@ -156,7 +156,7 @@ 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. @@ -169,9 +169,14 @@ typedef enum UndoTypeFlags { * This is typically used for undo systems that store both before/after states. */ UNDOTYPE_FLAG_DECODE_ACTIVE_STEP = 1 << 1, -} UndoTypeFlags; +} eUndoTypeFlags; + +/* -------------------------------------------------------------------- */ +/** \name Public Undo Types + * + * Expose since we need to perform operations on specific undo types (rarely). + * \{ */ -/* Expose since we need to perform operations on specific undo types (rarely). */ extern const UndoType *BKE_UNDOSYS_TYPE_IMAGE; extern const UndoType *BKE_UNDOSYS_TYPE_MEMFILE; extern const UndoType *BKE_UNDOSYS_TYPE_PAINTCURVE; @@ -179,17 +184,25 @@ extern const UndoType *BKE_UNDOSYS_TYPE_PARTICLE; extern const UndoType *BKE_UNDOSYS_TYPE_SCULPT; extern const UndoType *BKE_UNDOSYS_TYPE_TEXT; +/** \} */ + #define BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(ty) ELEM(ty, BKE_UNDOSYS_TYPE_IMAGE) 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); +/* name optional */ bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name); void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain); +/* called after 'BKE_undosys_stack_init_from_main' */ void BKE_undosys_stack_init_from_context(UndoStack *ustack, struct bContext *C); UndoStep *BKE_undosys_stack_active_with_type(UndoStack *ustack, const UndoType *ut); UndoStep *BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const UndoType *ut); +/** + * \param steps: Limit the number of undo steps. + * \param memory_limit: Limit the amount of memory used by the undo stack. + */ void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size_t memory_limit); #define BKE_undosys_stack_limit_steps_and_memory_defaults(ustack) \ BKE_undosys_stack_limit_steps_and_memory(ustack, U.undosteps, (size_t)U.undomemory * 1024 * 1024) @@ -197,18 +210,23 @@ void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size void BKE_undosys_stack_group_begin(UndoStack *ustack); void BKE_undosys_stack_group_end(UndoStack *ustack); -/* Only some UndoType's require init. */ +/** + * Only some UndoType's require init. + */ UndoStep *BKE_undosys_step_push_init_with_type(UndoStack *ustack, struct bContext *C, const char *name, 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); +/** + * \param C: Can be NULL from some callers if their encoding function doesn't need it + */ +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, @@ -216,40 +234,117 @@ UndoStep *BKE_undosys_step_find_by_name_with_type(UndoStack *ustack, UndoStep *BKE_undosys_step_find_by_type(UndoStack *ustack, const UndoType *ut); UndoStep *BKE_undosys_step_find_by_name(UndoStack *ustack, const char *name); +/** + * Return direction of the undo/redo from `us_reference` (or `ustack->step_active` if NULL), and + * `us_target`. + * + * \note If `us_reference` and `us_target` are the same, we consider this is an undo. + * + * \return -1 for undo, 1 for redo, 0 in case of error. + */ eUndoStepDir BKE_undosys_step_calc_direction(const UndoStack *ustack, const UndoStep *us_target, const UndoStep *us_reference); +/** + * 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 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). + */ bool BKE_undosys_step_load_data_ex(UndoStack *ustack, struct bContext *C, UndoStep *us_target, UndoStep *us_reference, - const bool use_skip); + bool use_skip); +/** + * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. + */ bool BKE_undosys_step_load_data(UndoStack *ustack, struct bContext *C, UndoStep *us_target); -void BKE_undosys_step_load_from_index(UndoStack *ustack, struct bContext *C, const int index); +/** + * Undo/Redo until the step matching given `index` in the undo stack becomes the active + * (currently loaded) one. + */ +void BKE_undosys_step_load_from_index(UndoStack *ustack, struct bContext *C, int index); +/** + * Undo until `us_target` step becomes the active (currently loaded) one. + * + * \warning This function assumes that the given target step is _before_ current active one. + * + * \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 **before** the given one + * (if the given one has to be skipped). + */ bool BKE_undosys_step_undo_with_data_ex(UndoStack *ustack, struct bContext *C, UndoStep *us, bool use_skip); +/** + * Undo until `us_target` step becomes the active (currently loaded) one. + * + * \note See #BKE_undosys_step_undo_with_data_ex for details. + */ bool BKE_undosys_step_undo_with_data(UndoStack *ustack, struct bContext *C, UndoStep *us_target); +/** + * Undo one step from current active (currently loaded) one. + */ bool BKE_undosys_step_undo(UndoStack *ustack, struct bContext *C); +/** + * Redo until `us_target` step becomes the active (currently loaded) one. + * + * \warning This function assumes that the given target step is _after_ current active one. + * + * \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 **after** the given one + * (if the given one has to be skipped). + */ bool BKE_undosys_step_redo_with_data_ex(UndoStack *ustack, struct bContext *C, UndoStep *us, bool use_skip); +/** + * Redo until `us_target` step becomes the active (currently loaded) one. + * + * \note See #BKE_undosys_step_redo_with_data_ex for details. + */ bool BKE_undosys_step_redo_with_data(UndoStack *ustack, struct bContext *C, UndoStep *us_target); +/** + * Redo one step from current active one. + */ bool BKE_undosys_step_redo(UndoStack *ustack, struct bContext *C); +/** + * Useful when we want to diff against previous undo data but can't be sure the types match. + */ UndoStep *BKE_undosys_step_same_type_next(UndoStep *us); +/** + * Useful when we want to diff against previous undo data but can't be sure the types match. + */ UndoStep *BKE_undosys_step_same_type_prev(UndoStep *us); -/* Type System */ +/* Type System. */ + +/** + * Similar to #WM_operatortype_append + */ UndoType *BKE_undosys_type_append(void (*undosys_fn)(UndoType *)); void BKE_undosys_type_free_all(void); -/* ID Accessor */ +/* ID Accessor. */ + #if 0 /* functionality is only used internally for now. */ void BKE_undosys_foreach_ID_ref(UndoStack *ustack, UndoTypeForEachIDRefFn foreach_ID_ref_fn, diff --git a/source/blender/blenkernel/BKE_unit.h b/source/blender/blenkernel/BKE_unit.h index b28215a72d1..505cfee3adf 100644 --- a/source/blender/blenkernel/BKE_unit.h +++ b/source/blender/blenkernel/BKE_unit.h @@ -26,9 +26,11 @@ extern "C" { struct UnitSettings; -/* in all cases the value is assumed to be scaled by the user preference */ +/* In all cases the value is assumed to be scaled by the user-preference. */ -/* humanly readable representation of a value in units (used for button drawing) */ +/** + * Humanly readable representation of a value in units (used for button drawing). + */ size_t BKE_unit_value_as_string_adaptive( char *str, int len_max, double value, int prec, int system, int type, bool split, bool pad); size_t BKE_unit_value_as_string(char *str, @@ -39,29 +41,59 @@ size_t BKE_unit_value_as_string(char *str, const struct UnitSettings *settings, bool pad); -/* replace units with values, used before python button evaluation */ +/** + * Replace units with values, used before python button evaluation. + * + * Make a copy of the string that replaces the units with numbers. + * This is only used when evaluating user input and can afford to be a bit slower + * + * This is to be used before python evaluation so: + * `10.1km -> 10.1*1000.0` + * ...will be resolved by Python. + * + * Values will be split by an add sign: + * `5'2" -> 5*0.3048 + 2*0.0254` + * + * \param str_prev: is optional, when valid it is used to get a base unit when none is set. + * + * \return True of a change was made. + */ bool BKE_unit_replace_string( char *str, int len_max, const char *str_prev, double scale_pref, int system, int type); -/* return true if the string contains any valid unit for the given type */ +/** + * \return true if the string contains any valid unit for the given type. + */ bool BKE_unit_string_contains_unit(const char *str, int type); -/* If user does not specify a unit, this converts it to the unit from the settings. */ +/** + * If user does not specify a unit, this converts it to the unit from the settings. + */ double BKE_unit_apply_preferred_unit(const struct UnitSettings *settings, int type, double value); -/* make string keyboard-friendly: 10µm --> 10um */ +/** + * Make string keyboard-friendly, e.g: `10µm -> 10um`. + */ void BKE_unit_name_to_alt(char *str, int len_max, const char *orig_str, int system, int type); -/* the size of the unit used for this value (used for calculating the ckickstep) */ +/** + * The size of the unit used for this value (used for calculating the click-step). + */ double BKE_unit_closest_scalar(double value, int system, int type); -/* base scale for these units */ +/** + * Base scale for these units. + */ double BKE_unit_base_scalar(int system, int type); -/* return true is the unit system exists */ +/** + * \return true is the unit system exists. + */ bool BKE_unit_is_valid(int system, int type); -/* loop over scales, could add names later */ +/** + * Loop over scales, could add names later. + */ // double bUnit_Iter(void **unit, char **name, int system, int type); void BKE_unit_system_get(int system, int type, const void **r_usys_pt, int *r_len); @@ -73,7 +105,7 @@ const char *BKE_unit_identifier_get(const void *usys_pt, int index); double BKE_unit_scalar_get(const void *usys_pt, int index); bool BKE_unit_is_suppressed(const void *usys_pt, int index); -/* aligned with PropertyUnit */ +/** Aligned with #PropertyUnit. */ enum { B_UNIT_NONE = 0, B_UNIT_LENGTH = 1, diff --git a/source/blender/blenkernel/BKE_font.h b/source/blender/blenkernel/BKE_vfont.h index 827ae1b6a0f..3397f2ef82a 100644 --- a/source/blender/blenkernel/BKE_font.h +++ b/source/blender/blenkernel/BKE_vfont.h @@ -84,6 +84,9 @@ bool BKE_vfont_to_curve_ex(struct Object *ob, bool *r_text_free, struct CharTrans **r_chartransdata); bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase); +/** + * \warning Expects to have access to evaluated data (i.e. passed object should be evaluated one). + */ bool BKE_vfont_to_curve(struct Object *ob, int mode); void BKE_vfont_build_char(struct Curve *cu, struct ListBase *nubase, @@ -93,7 +96,7 @@ void BKE_vfont_build_char(struct Curve *cu, float ofsy, float rot, int charidx, - const float fsize); + float fsize); int BKE_vfont_select_get(struct Object *ob, int *r_start, int *r_end); void BKE_vfont_select_clamp(struct Object *ob); @@ -101,7 +104,7 @@ void BKE_vfont_select_clamp(struct Object *ob); void BKE_vfont_clipboard_free(void); void BKE_vfont_clipboard_set(const char32_t *text_buf, const struct CharInfo *info_buf, - const size_t len); + size_t len); void BKE_vfont_clipboard_get(char32_t **r_text_buf, struct CharInfo **r_info_buf, size_t *r_len_utf8, diff --git a/source/blender/blenkernel/BKE_vfontdata.h b/source/blender/blenkernel/BKE_vfontdata.h new file mode 100644 index 00000000000..01ca59828fc --- /dev/null +++ b/source/blender/blenkernel/BKE_vfontdata.h @@ -0,0 +1,66 @@ +/* + * 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; + +/** + * Construct a new #VFontData structure from free-type font data in `pf`. + * + * \param pf: The font data. + * \retval A new #VFontData structure, or NULL if unable to load. + */ +VFontData *BKE_vfontdata_from_freetypefont(struct PackedFile *pf); +VFontData *BKE_vfontdata_copy(const VFontData *vfont_src, 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 5fe0d54c2cf..8b42de7303d 100644 --- a/source/blender/blenkernel/BKE_volume.h +++ b/source/blender/blenkernel/BKE_volume.h @@ -88,6 +88,7 @@ const char *BKE_volume_grids_frame_filepath(const struct Volume *volume); const VolumeGrid *BKE_volume_grid_get_for_read(const struct Volume *volume, int grid_index); VolumeGrid *BKE_volume_grid_get_for_write(struct Volume *volume, int grid_index); const VolumeGrid *BKE_volume_grid_active_get_for_read(const struct Volume *volume); +/* Tries to find a grid with the given name. Make sure that the volume has been loaded. */ const VolumeGrid *BKE_volume_grid_find_for_read(const struct Volume *volume, const char *name); /* Grid @@ -115,9 +116,13 @@ void BKE_volume_grid_unload(const struct Volume *volume, const struct VolumeGrid bool BKE_volume_grid_is_loaded(const struct VolumeGrid *grid); /* Metadata */ + const char *BKE_volume_grid_name(const struct VolumeGrid *grid); VolumeGridType BKE_volume_grid_type(const struct VolumeGrid *grid); int BKE_volume_grid_channels(const struct VolumeGrid *grid); +/** + * Transformation from index space to object space. + */ void BKE_volume_grid_transform_matrix(const struct VolumeGrid *grid, float mat[4][4]); /* Volume Editing @@ -158,8 +163,9 @@ bool BKE_volume_save(const struct Volume *volume, * file or copy shared grids to make them writeable. */ #ifdef __cplusplus -# include "BLI_float3.hh" # include "BLI_float4x4.hh" +# include "BLI_math_vec_types.hh" +# include "BLI_string_ref.hh" bool BKE_volume_min_max(const Volume *volume, blender::float3 &r_min, blender::float3 &r_max); @@ -167,10 +173,19 @@ 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); +/** + * Return a new grid pointer with only the metadata and transform changed. + * This is useful for instances, where there is a separate transform on top of the original + * grid transform that must be applied for some operations that only take a grid argument. + */ openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid, const blender::float4x4 &transform); @@ -179,7 +194,7 @@ openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const struct Volume const struct VolumeGrid *grid); openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const struct Volume *volume, struct VolumeGrid *grid, - const bool clear); + bool clear); VolumeGridType BKE_volume_grid_type_openvdb(const openvdb::GridBase &grid); @@ -219,9 +234,7 @@ auto BKE_volume_grid_type_operation(const VolumeGridType grid_type, OpType &&op) } openvdb::GridBase::Ptr BKE_volume_grid_create_with_changed_resolution( - const VolumeGridType grid_type, - const openvdb::GridBase &old_grid, - const float resolution_factor); + const VolumeGridType grid_type, const openvdb::GridBase &old_grid, float resolution_factor); # endif #endif diff --git a/source/blender/blenkernel/BKE_volume_to_mesh.hh b/source/blender/blenkernel/BKE_volume_to_mesh.hh index 1f6e89636c4..123cb33f24f 100644 --- a/source/blender/blenkernel/BKE_volume_to_mesh.hh +++ b/source/blender/blenkernel/BKE_volume_to_mesh.hh @@ -14,6 +14,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#pragma once + +#include "BLI_span.hh" + #include "DNA_modifier_types.h" #ifdef WITH_OPENVDB @@ -33,10 +37,49 @@ 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); + float threshold, + float adaptivity); + +/** + * Convert an OpenVDB volume grid to corresponding mesh data: vertex positions and quad and + * triangle indices. + */ +struct OpenVDBMeshData volume_to_mesh_data(const openvdb::GridBase &grid, + const VolumeToMeshResolution &resolution, + float threshold, + float adaptivity); + +/** + * 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, + int vert_offset, + int poly_offset, + int loop_offset, + MutableSpan<MVert> verts, + MutableSpan<MPoly> polys, + MutableSpan<MLoop> loops); + #endif } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_workspace.h b/source/blender/blenkernel/BKE_workspace.h index 82a4e5fe08e..0f609be67de 100644 --- a/source/blender/blenkernel/BKE_workspace.h +++ b/source/blender/blenkernel/BKE_workspace.h @@ -31,16 +31,27 @@ struct bScreen; struct bToolRef; /* -------------------------------------------------------------------- */ -/* Create, delete, init */ +/** \name Create, Delete, Initialize + * \{ */ struct WorkSpace *BKE_workspace_add(struct Main *bmain, const char *name); +/** + * Remove \a workspace by freeing itself and its data. This is a higher-level wrapper that + * calls #workspace_free_data (through #BKE_id_free) to free the workspace data, and frees + * other data-blocks owned by \a workspace and its layouts (currently that is screens only). + * + * Always use this to remove (and free) workspaces. Don't free non-ID workspace members here. + */ void BKE_workspace_remove(struct Main *bmain, struct WorkSpace *workspace); struct WorkSpaceInstanceHook *BKE_workspace_instance_hook_create(const struct Main *bmain, - const int winid); + int winid); void BKE_workspace_instance_hook_free(const struct Main *bmain, struct WorkSpaceInstanceHook *hook); +/** + * Add a new layout to \a workspace for \a screen. + */ struct WorkSpaceLayout *BKE_workspace_layout_add(struct Main *bmain, struct WorkSpace *workspace, struct bScreen *screen, @@ -51,29 +62,51 @@ void BKE_workspace_layout_remove(struct Main *bmain, void BKE_workspace_relations_free(ListBase *relation_list); +/** \} */ + /* -------------------------------------------------------------------- */ -/* General Utils */ +/** \name General Utilities + * \{ */ struct WorkSpaceLayout *BKE_workspace_layout_find(const struct WorkSpace *workspace, const struct bScreen *screen) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT; +/** + * Find the layout for \a screen without knowing which workspace to look in. + * Can also be used to find the workspace that contains \a screen. + * + * \param r_workspace: Optionally return the workspace that contains the + * looked up layout (if found). + */ struct WorkSpaceLayout *BKE_workspace_layout_find_global(const struct Main *bmain, const struct bScreen *screen, struct WorkSpace **r_workspace) ATTR_NONNULL(1, 2); +/** + * Circular workspace layout iterator. + * + * \param callback: Custom function which gets executed for each layout. + * Can return false to stop iterating. + * \param arg: Custom data passed to each \a callback call. + * + * \return the layout at which \a callback returned false. + */ struct WorkSpaceLayout *BKE_workspace_layout_iter_circular( const struct WorkSpace *workspace, struct WorkSpaceLayout *start, bool (*callback)(const struct WorkSpaceLayout *layout, void *arg), void *arg, - const bool iter_backward); + bool iter_backward); void BKE_workspace_tool_remove(struct WorkSpace *workspace, struct bToolRef *tref) ATTR_NONNULL(1, 2); +/** \} */ + /* -------------------------------------------------------------------- */ -/* Getters/Setters */ +/** \name Getters/Setters + * \{ */ #define GETTER_ATTRS ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT #define SETTER_ATTRS ATTR_NONNULL(1) @@ -81,16 +114,31 @@ void BKE_workspace_tool_remove(struct WorkSpace *workspace, struct bToolRef *tre struct WorkSpace *BKE_workspace_active_get(struct WorkSpaceInstanceHook *hook) GETTER_ATTRS; void BKE_workspace_active_set(struct WorkSpaceInstanceHook *hook, struct WorkSpace *workspace) SETTER_ATTRS; +/** + * Get the layout that is active for \a hook (which is the visible layout for the active workspace + * in \a hook). + */ struct WorkSpaceLayout *BKE_workspace_active_layout_get(const struct WorkSpaceInstanceHook *hook) GETTER_ATTRS; +/** + * \brief Activate a layout + * + * Sets \a layout as active for \a workspace when activated through or already active in \a hook. + * So when the active workspace of \a hook is \a workspace, \a layout becomes the active layout of + * \a hook too. See #BKE_workspace_active_set(). + * + * \a workspace does not need to be active for this. + * + * #WorkSpaceInstanceHook.act_layout should only be modified directly to update the layout pointer. + */ void BKE_workspace_active_layout_set(struct WorkSpaceInstanceHook *hook, - const int winid, + int winid, struct WorkSpace *workspace, struct WorkSpaceLayout *layout) SETTER_ATTRS; struct bScreen *BKE_workspace_active_screen_get(const struct WorkSpaceInstanceHook *hook) GETTER_ATTRS; void BKE_workspace_active_screen_set(struct WorkSpaceInstanceHook *hook, - const int winid, + int winid, struct WorkSpace *workspace, struct bScreen *screen) SETTER_ATTRS; @@ -100,6 +148,9 @@ void BKE_workspace_layout_name_set(struct WorkSpace *workspace, const char *new_name) ATTR_NONNULL(); struct bScreen *BKE_workspace_layout_screen_get(const struct WorkSpaceLayout *layout) GETTER_ATTRS; +/** + * Get the layout to be activated should \a workspace become or be the active workspace in \a hook. + */ struct WorkSpaceLayout *BKE_workspace_active_layout_for_workspace_get( const struct WorkSpaceInstanceHook *hook, const struct WorkSpace *workspace) GETTER_ATTRS; @@ -111,6 +162,8 @@ void BKE_workspace_id_tag_all_visible(struct Main *bmain, int tag) ATTR_NONNULL( #undef GETTER_ATTRS #undef SETTER_ATTRS +/** \} */ + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_writeavi.h b/source/blender/blenkernel/BKE_writeavi.h index 97f998cc1c1..c20de4df901 100644 --- a/source/blender/blenkernel/BKE_writeavi.h +++ b/source/blender/blenkernel/BKE_writeavi.h @@ -63,7 +63,11 @@ typedef struct bMovieHandle { void (*context_free)(void *context_v); } bMovieHandle; -bMovieHandle *BKE_movie_handle_get(const char imtype); +bMovieHandle *BKE_movie_handle_get(char imtype); + +/** + * \note Similar to #BKE_image_path_from_imformat() + */ void BKE_movie_filepath_get(char *string, const struct RenderData *rd, bool preview, diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 24de91959bb..6d6579f49f6 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 @@ -85,7 +88,9 @@ set(SRC 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 @@ -95,6 +100,7 @@ set(SRC intern/blender_undo.c intern/blender_user_menu.c intern/blendfile.c + intern/blendfile_link_append.c intern/boids.c intern/bpath.c intern/brush.c @@ -112,15 +118,15 @@ set(SRC intern/context.c intern/crazyspace.c intern/cryptomatte.cc - intern/curve.c + intern/curve.cc intern/curve_bevel.c intern/curve_convert.c intern/curve_decimate.c intern/curve_deform.c intern/curve_eval.cc intern/curve_to_mesh_convert.cc - intern/curveprofile.c - intern/customdata.c + intern/curveprofile.cc + intern/customdata.cc intern/customdata_file.c intern/data_transfer.c intern/deform.c @@ -138,7 +144,6 @@ set(SRC 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 @@ -151,15 +156,17 @@ set(SRC intern/gpencil_curve.c intern/gpencil_geom.cc intern/gpencil_modifier.c - intern/hair.c + intern/hair.cc intern/icons.cc intern/icons_rasterize.c intern/idprop.c + intern/idprop_create.cc + intern/idprop_serialize.cc intern/idprop_utils.c intern/idtype.c intern/image.c intern/image_gen.c - intern/image_gpu.c + intern/image_gpu.cc intern/image_save.c intern/ipo.c intern/kelvinlet.c @@ -172,6 +179,7 @@ set(SRC intern/lib_id.c intern/lib_id_delete.c intern/lib_id_eval.c + intern/lib_id_remapper.cc intern/lib_override.c intern/lib_query.c intern/lib_remap.c @@ -187,9 +195,10 @@ 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.cc + intern/mesh_debug.cc intern/mesh_evaluate.cc intern/mesh_fair.cc intern/mesh_iterators.c @@ -221,7 +230,8 @@ set(SRC intern/multires_versioning.c intern/nla.c intern/node.cc - intern/object.c + intern/node_tree_update.cc + intern/object.cc intern/object_deform.c intern/object_dupli.cc intern/object_facemap.c @@ -268,6 +278,7 @@ set(SRC intern/subdiv_eval.c intern/subdiv_foreach.c intern/subdiv_mesh.c + intern/subdiv_modifier.c intern/subdiv_stats.c intern/subdiv_topology.c intern/subsurf_ccg.c @@ -282,8 +293,11 @@ set(SRC intern/tracking_solver.c intern/tracking_stabilize.c intern/tracking_util.c + intern/type_conversions.cc 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 @@ -306,6 +320,7 @@ set(SRC 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 @@ -318,6 +333,7 @@ set(SRC BKE_blender_user_menu.h BKE_blender_version.h BKE_blendfile.h + BKE_blendfile_link_append.h BKE_boids.h BKE_bpath.h BKE_brush.h @@ -357,7 +373,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 @@ -370,6 +385,7 @@ set(SRC BKE_hair.h BKE_icons.h BKE_idprop.h + BKE_idprop.hh BKE_idtype.h BKE_image.h BKE_image_save.h @@ -411,6 +427,7 @@ set(SRC BKE_multires.h BKE_nla.h BKE_node.h + BKE_node_tree_update.h BKE_object.h BKE_object_deform.h BKE_object_facemap.h @@ -442,14 +459,18 @@ set(SRC BKE_subdiv_eval.h BKE_subdiv_foreach.h BKE_subdiv_mesh.h + BKE_subdiv_modifier.h BKE_subdiv_topology.h BKE_subsurf.h BKE_text.h BKE_text_suggestions.h BKE_texture.h BKE_tracking.h + BKE_type_conversions.hh 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 @@ -463,6 +484,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 @@ -499,6 +521,9 @@ set(LIB bf_rna bf_shader_fx bf_simulation + + # For `vfontdata_freetype.c`. + ${FREETYPE_LIBRARIES} ${BROTLI_LIBRARIES} ) if(WITH_BINRELOC) @@ -788,14 +813,20 @@ 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/bpath_test.cc intern/cryptomatte_test.cc intern/fcurve_test.cc + intern/idprop_serialize_test.cc intern/lattice_deform_test.cc intern/layer_test.cc + intern/lib_id_remapper_test.cc intern/lib_id_test.cc + intern/lib_remap_test.cc intern/tracking_test.cc ) set(TEST_INC diff --git a/source/blender/blenkernel/intern/CCGSubSurf.c b/source/blender/blenkernel/intern/CCGSubSurf.c index 67e7b890548..74f848ac580 100644 --- a/source/blender/blenkernel/intern/CCGSubSurf.c +++ b/source/blender/blenkernel/intern/CCGSubSurf.c @@ -939,7 +939,6 @@ void ccgSubSurf__effectedFaceNeighbors(CCGSubSurf *ss, *numEdges = numE; } -/* copy face grid coordinates to other places */ CCGError ccgSubSurf_updateFromFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF) { int i, S, x, gridSize, cornerIdx, subdivLevels; @@ -986,7 +985,6 @@ CCGError ccgSubSurf_updateFromFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF return eCCGError_None; } -/* copy other places to face grid coordinates */ CCGError ccgSubSurf_updateToFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF) { int i, S, x, gridSize, cornerIdx, subdivLevels; @@ -1035,8 +1033,6 @@ CCGError ccgSubSurf_updateToFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, return eCCGError_None; } -/* stitch together face grids, averaging coordinates at edges - * and vertices, for multires displacements */ CCGError ccgSubSurf_stitchFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF) { CCGVert **effectedV; diff --git a/source/blender/blenkernel/intern/CCGSubSurf.h b/source/blender/blenkernel/intern/CCGSubSurf.h index a9e0d6882c1..9349c33d72a 100644 --- a/source/blender/blenkernel/intern/CCGSubSurf.h +++ b/source/blender/blenkernel/intern/CCGSubSurf.h @@ -100,13 +100,30 @@ CCGError ccgSubSurf_syncFaceDel(CCGSubSurf *ss, CCGFaceHDL fHDL); CCGError ccgSubSurf_processSync(CCGSubSurf *ss); +/** + * Copy face grid coordinates to other places. + */ CCGError ccgSubSurf_updateFromFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF); +/** + * Copy other places to face grid coordinates. + */ CCGError ccgSubSurf_updateToFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF); +/** + * Update normals for specified faces. + */ CCGError ccgSubSurf_updateNormals(CCGSubSurf *ss, CCGFace **effectedF, int numEffectedF); +/** + * Compute subdivision levels from a given starting point, used by multi-res subdivide/propagate, + * by filling in coordinates at a certain level, and then subdividing that up to the highest level. + */ CCGError ccgSubSurf_updateLevels(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF); +/** + * Stitch together face grids, averaging coordinates at edges and vertices, for multi-res + * displacements. + */ CCGError ccgSubSurf_stitchFaces(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF); CCGError ccgSubSurf_setSubdivisionLevels(CCGSubSurf *ss, int subdivisionLevels); diff --git a/source/blender/blenkernel/intern/CCGSubSurf_legacy.c b/source/blender/blenkernel/intern/CCGSubSurf_legacy.c index 99ea1fb9607..e19e01ec034 100644 --- a/source/blender/blenkernel/intern/CCGSubSurf_legacy.c +++ b/source/blender/blenkernel/intern/CCGSubSurf_legacy.c @@ -1309,7 +1309,6 @@ void ccgSubSurf__sync_legacy(CCGSubSurf *ss) /* ** Public API exposed to other areas which depends on old CCG code. ** */ -/* Update normals for specified faces. */ CCGError ccgSubSurf_updateNormals(CCGSubSurf *ss, CCGFace **effectedF, int numEffectedF) { CCGVert **effectedV; @@ -1344,9 +1343,6 @@ CCGError ccgSubSurf_updateNormals(CCGSubSurf *ss, CCGFace **effectedF, int numEf return eCCGError_None; } -/* compute subdivision levels from a given starting point, used by - * multires subdivide/propagate, by filling in coordinates at a - * certain level, and then subdividing that up to the highest level */ CCGError ccgSubSurf_updateLevels(CCGSubSurf *ss, int lvl, CCGFace **effectedF, int numEffectedF) { CCGVert **effectedV; diff --git a/source/blender/blenkernel/intern/DerivedMesh.cc b/source/blender/blenkernel/intern/DerivedMesh.cc index 59e81938e79..d0d19ff199d 100644 --- a/source/blender/blenkernel/intern/DerivedMesh.cc +++ b/source/blender/blenkernel/intern/DerivedMesh.cc @@ -38,9 +38,9 @@ #include "BLI_array.h" #include "BLI_bitmap.h" #include "BLI_blenlib.h" -#include "BLI_float2.hh" #include "BLI_linklist.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_task.h" #include "BLI_task.hh" #include "BLI_utildefines.h" @@ -295,10 +295,6 @@ static CustomData *dm_getPolyCData(DerivedMesh *dm) return &dm->polyData; } -/** - * Utility function to initialize a DerivedMesh's function pointers to - * the default implementation (for those functions which have a default) - */ void DM_init_funcs(DerivedMesh *dm) { /* default function implementations */ @@ -335,11 +331,6 @@ void DM_init_funcs(DerivedMesh *dm) dm->getLoopDataArray = DM_get_loop_data_layer; } -/** - * Utility function to initialize a DerivedMesh for the desired number - * of vertices, edges and faces (doesn't allocate memory for them, just - * sets up the custom data layers) - */ void DM_init(DerivedMesh *dm, DerivedMeshType type, int numVerts, @@ -368,10 +359,6 @@ void DM_init(DerivedMesh *dm, copy_vn_i(dm->polyData.typemap, CD_NUMTYPES, -1); } -/** - * Utility function to initialize a DerivedMesh for the desired number - * of vertices, edges and faces, with a layer setup copied from source - */ void DM_from_template_ex(DerivedMesh *dm, DerivedMesh *source, DerivedMeshType type, @@ -485,12 +472,6 @@ void DM_ensure_normals(DerivedMesh *dm) BLI_assert((dm->dirty & DM_DIRTY_NORMALS) == 0); } -/** - * Ensure the array is large enough - * - * \note This function must always be thread-protected by caller. - * It should only be used by internal code. - */ void DM_ensure_looptri_data(DerivedMesh *dm) { const unsigned int totpoly = dm->numPolyData; @@ -519,11 +500,11 @@ void DM_ensure_looptri_data(DerivedMesh *dm) } } -/** Utility function to convert an (evaluated) Mesh to a shape key block. */ -/* Just a shallow wrapper around BKE_keyblock_convert_from_mesh, - * that ensures both evaluated mesh and original one has same number of vertices. */ void BKE_mesh_runtime_eval_to_meshkey(Mesh *me_deformed, Mesh *me, KeyBlock *kb) { + /* Just a shallow wrapper around #BKE_keyblock_convert_from_mesh, + * that ensures both evaluated mesh and original one has same number of vertices. */ + const int totvert = me_deformed->totvert; if (totvert == 0 || me->totvert == 0 || me->totvert != totvert) { @@ -533,11 +514,6 @@ void BKE_mesh_runtime_eval_to_meshkey(Mesh *me_deformed, Mesh *me, KeyBlock *kb) BKE_keyblock_convert_from_mesh(me_deformed, me->key, kb); } -/** - * set the CD_FLAG_NOCOPY flag in custom data layers where the mask is - * zero for the layer type, so only layer types specified by the mask - * will be copied - */ void DM_set_only_copy(DerivedMesh *dm, const CustomData_MeshMasks *mask) { CustomData_set_only_copy(&dm->vertData, mask->vmask); @@ -658,11 +634,6 @@ void DM_copy_vert_data( CustomData_copy_data(&source->vertData, &dest->vertData, source_index, dest_index, count); } -/** - * interpolates vertex data from the vertices indexed by src_indices in the - * source mesh using the given weights and stores the result in the vertex - * indexed by dest_index in the dest mesh - */ void DM_interp_vert_data(DerivedMesh *source, DerivedMesh *dest, int *src_indices, @@ -804,28 +775,6 @@ static void mesh_calc_modifier_final_normals(const Mesh *mesh_input, /* Compute normals. */ const bool do_loop_normals = ((mesh_input->flag & ME_AUTOSMOOTH) != 0 || (final_datamask->lmask & CD_MASK_NORMAL) != 0); - /* Some modifiers may need this info from their target (other) object, - * simpler to generate it here as well. - * Note that they will always be generated when no loop normals are computed, - * since they are needed by drawing code. */ - const bool do_poly_normals = ((final_datamask->pmask & CD_MASK_NORMAL) != 0); - - /* In case we also need poly normals, add the layer and compute them here - * (BKE_mesh_calc_normals_split() assumes that if that data exists, it is always valid). */ - if (do_poly_normals) { - 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_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!). */ @@ -843,11 +792,7 @@ static void mesh_calc_modifier_final_normals(const Mesh *mesh_input, * normals and will also have to calculate normals on the fly, try avoid * this where possible since calculating polygon normals isn't fast, * note that this isn't a problem for subsurf (only quads) or editmode - * which deals with drawing differently. - * - * Only calc vertex normals if they are flagged as dirty. - * If using loop normals, poly nors have already been computed. - */ + * which deals with drawing differently. */ if (!do_loop_normals) { BKE_mesh_ensure_normals_for_display(mesh_final); } @@ -886,33 +831,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, 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. @@ -928,14 +846,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 @@ -955,6 +866,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(); } @@ -986,6 +901,7 @@ static void mesh_calc_modifiers(struct Depsgraph *depsgraph, * constructive modifier is executed, or a deform modifier needs normals * or certain data layers. */ Mesh *mesh_input = (Mesh *)ob->data; + BKE_mesh_assert_normals_dirty_or_calculated(mesh_input); Mesh *mesh_final = nullptr; Mesh *mesh_deform = nullptr; /* This geometry set contains the non-mesh data that might be generated by modifiers. */ @@ -1177,14 +1093,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. */ @@ -1525,26 +1433,6 @@ static void editbmesh_calc_modifier_final_normals(Mesh *mesh_final, const bool do_loop_normals = ((mesh_final->flag & ME_AUTOSMOOTH) != 0 || (final_datamask->lmask & CD_MASK_NORMAL) != 0); - /* Some modifiers may need this info from their target (other) object, - * simpler to generate it here as well. */ - const bool do_poly_normals = ((final_datamask->pmask & CD_MASK_NORMAL) != 0); - - /* In case we also need poly normals, add the layer and compute them here - * (BKE_mesh_calc_normals_split() assumes that if that data exists, it is always valid). */ - if (do_poly_normals) { - 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_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 */ @@ -1875,31 +1763,12 @@ static void mesh_build_extra_data(struct Depsgraph *depsgraph, Object *ob, Mesh } } -static void mesh_runtime_check_normals_valid(const Mesh *mesh) -{ - UNUSED_VARS_NDEBUG(mesh); - BLI_assert(!(mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL)); - BLI_assert(!(mesh->runtime.cd_dirty_loop & CD_MASK_NORMAL)); - BLI_assert(!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL)); -} - static void mesh_build_data(struct Depsgraph *depsgraph, Scene *scene, Object *ob, const CustomData_MeshMasks *dataMask, const bool need_mapping) { - BLI_assert(ob->type == OB_MESH); - - /* Evaluated meshes aren't supposed to be created on original instances. If you do, - * they aren't cleaned up properly on mode switch, causing crashes, e.g T58150. */ - BLI_assert(ob->id.tag & LIB_TAG_COPIED_ON_WRITE); - - BKE_object_free_derived_caches(ob); - if (DEG_is_active(depsgraph)) { - BKE_sculpt_update_object_before_eval(ob); - } - #if 0 /* XXX This is already taken care of in mesh_calc_modifiers()... */ if (need_mapping) { /* Also add the flag so that it is recorded in lastDataMask. */ @@ -1934,9 +1803,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_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; @@ -1957,7 +1826,6 @@ static void mesh_build_data(struct Depsgraph *depsgraph, } } - mesh_runtime_check_normals_valid(mesh_eval); mesh_build_extra_data(depsgraph, ob, mesh_eval); } @@ -1967,15 +1835,7 @@ static void editbmesh_build_data(struct Depsgraph *depsgraph, BMEditMesh *em, CustomData_MeshMasks *dataMask) { - BLI_assert(obedit->id.tag & LIB_TAG_COPIED_ON_WRITE); - - BKE_object_free_derived_caches(obedit); - if (DEG_is_active(depsgraph)) { - BKE_sculpt_update_object_before_eval(obedit); - } - - BKE_editmesh_free_derived_caches(em); - + Mesh *mesh = static_cast<Mesh *>(obedit->data); Mesh *me_cage; Mesh *me_final; GeometrySet *non_mesh_components; @@ -1983,15 +1843,33 @@ static void editbmesh_build_data(struct Depsgraph *depsgraph, editbmesh_calc_modifiers( depsgraph, scene, obedit, em, dataMask, &me_cage, &me_final, &non_mesh_components); - em->mesh_eval_final = me_final; - em->mesh_eval_cage = me_cage; - obedit->runtime.geometry_set_eval = non_mesh_components; + /* The modifier stack result is expected to share edit mesh pointer with the input. + * This is similar `mesh_calc_finalize()`. */ + BKE_mesh_free_editmesh(me_final); + BKE_mesh_free_editmesh(me_cage); + me_final->edit_mesh = me_cage->edit_mesh = em; + + /* Object has edit_mesh but is not in edit mode (object shares mesh datablock with another object + * with is in edit mode). + * Convert edit mesh to mesh until the draw manager can draw mesh wrapper which is not in the + * edit mode. */ + if (!(obedit->mode & OB_MODE_EDIT)) { + BKE_mesh_wrapper_ensure_mdata(me_final); + if (me_final != me_cage) { + BKE_mesh_wrapper_ensure_mdata(me_cage); + } + } - BKE_object_boundbox_calc_from_mesh(obedit, em->mesh_eval_final); + const bool is_mesh_eval_owned = (me_final != mesh->runtime.mesh_eval); + BKE_object_eval_assign_data(obedit, &me_final->id, is_mesh_eval_owned); - em->lastDataMask = *dataMask; + obedit->runtime.editmesh_eval_cage = me_cage; - mesh_runtime_check_normals_valid(em->mesh_eval_final); + obedit->runtime.geometry_set_eval = non_mesh_components; + + BKE_object_boundbox_calc_from_mesh(obedit, me_final); + + obedit->runtime.last_data_mask = *dataMask; } static void object_get_datamask(const Depsgraph *depsgraph, @@ -2047,9 +1925,25 @@ static void object_get_datamask(const Depsgraph *depsgraph, void makeDerivedMesh(struct Depsgraph *depsgraph, Scene *scene, Object *ob, - BMEditMesh *em, const CustomData_MeshMasks *dataMask) { + BLI_assert(ob->type == OB_MESH); + + /* Evaluated meshes aren't supposed to be created on original instances. If you do, + * they aren't cleaned up properly on mode switch, causing crashes, e.g T58150. */ + BLI_assert(ob->id.tag & LIB_TAG_COPIED_ON_WRITE); + + BKE_object_free_derived_caches(ob); + if (DEG_is_active(depsgraph)) { + BKE_sculpt_update_object_before_eval(ob); + } + + /* NOTE: Access the `edit_mesh` after freeing the derived caches, so that `ob->data` is restored + * to the pre-evaluated state. This is because the evaluated state is not necessarily sharing the + * `edit_mesh` pointer with the input. For example, if the object is first evaluated in the + * object mode, and then user in another scene moves object to edit mode. */ + BMEditMesh *em = ((Mesh *)ob->data)->edit_mesh; + bool need_mapping; CustomData_MeshMasks cddata_masks = *dataMask; object_get_datamask(depsgraph, ob, &cddata_masks, &need_mapping); @@ -2088,8 +1982,9 @@ Mesh *mesh_get_eval_final(struct Depsgraph *depsgraph, !CustomData_MeshMasks_are_matching(&(ob->runtime.last_data_mask), &cddata_masks) || (need_mapping && !ob->runtime.last_need_mapping)) { CustomData_MeshMasks_update(&cddata_masks, &ob->runtime.last_data_mask); - mesh_build_data( - depsgraph, scene, ob, &cddata_masks, need_mapping || ob->runtime.last_need_mapping); + + makeDerivedMesh(depsgraph, scene, ob, dataMask); + mesh_eval = BKE_object_get_evaluated_mesh(ob); } @@ -2104,6 +1999,15 @@ Mesh *mesh_get_eval_deform(struct Depsgraph *depsgraph, Object *ob, const CustomData_MeshMasks *dataMask) { + BMEditMesh *em = ((Mesh *)ob->data)->edit_mesh; + if (em != nullptr) { + /* There is no such a concept as deformed mesh in edit mode. + * Explicitly disallow this request so that the evaluated result is not modified with evaluated + * result from the wrong mode. */ + BLI_assert_msg(0, "Request of derformed mesh of object which is in edit mode"); + return nullptr; + } + /* This function isn't thread-safe and can't be used during evaluation. */ BLI_assert(DEG_is_evaluating(depsgraph) == false); @@ -2135,26 +2039,10 @@ Mesh *mesh_create_eval_final(Depsgraph *depsgraph, Object *ob, const CustomData_MeshMasks *dataMask) { - Mesh *final; - + Mesh *result; mesh_calc_modifiers( - depsgraph, scene, ob, true, false, dataMask, -1, false, false, nullptr, &final, nullptr); - - return final; -} - -Mesh *mesh_create_eval_final_index_render(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - const CustomData_MeshMasks *dataMask, - int index) -{ - Mesh *final; - - mesh_calc_modifiers( - depsgraph, scene, ob, true, false, dataMask, index, false, false, nullptr, &final, nullptr); - - return final; + depsgraph, scene, ob, true, false, dataMask, -1, false, false, nullptr, &result, nullptr); + return result; } Mesh *mesh_create_eval_no_deform(Depsgraph *depsgraph, @@ -2162,12 +2050,10 @@ Mesh *mesh_create_eval_no_deform(Depsgraph *depsgraph, Object *ob, const CustomData_MeshMasks *dataMask) { - Mesh *final; - + Mesh *result; mesh_calc_modifiers( - depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &final, nullptr); - - return final; + depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &result, nullptr); + return result; } Mesh *mesh_create_eval_no_deform_render(Depsgraph *depsgraph, @@ -2175,43 +2061,14 @@ Mesh *mesh_create_eval_no_deform_render(Depsgraph *depsgraph, Object *ob, const CustomData_MeshMasks *dataMask) { - Mesh *final; - + Mesh *result; mesh_calc_modifiers( - depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &final, nullptr); - - return final; + depsgraph, scene, ob, false, false, dataMask, -1, false, false, nullptr, &result, nullptr); + return result; } /***/ -Mesh *editbmesh_get_eval_cage_and_final(Depsgraph *depsgraph, - Scene *scene, - Object *obedit, - BMEditMesh *em, - const CustomData_MeshMasks *dataMask, - /* return args */ - Mesh **r_final) -{ - CustomData_MeshMasks cddata_masks = *dataMask; - - /* if there's no derived mesh or the last data mask used doesn't include - * the data we need, rebuild the derived mesh - */ - object_get_datamask(depsgraph, obedit, &cddata_masks, nullptr); - - if (!em->mesh_eval_cage || - !CustomData_MeshMasks_are_matching(&(em->lastDataMask), &cddata_masks)) { - editbmesh_build_data(depsgraph, scene, obedit, em, &cddata_masks); - } - - *r_final = em->mesh_eval_final; - if (em->mesh_eval_final) { - BLI_assert(!(em->mesh_eval_final->runtime.cd_dirty_vert & DM_DIRTY_NORMALS)); - } - return em->mesh_eval_cage; -} - Mesh *editbmesh_get_eval_cage(struct Depsgraph *depsgraph, Scene *scene, Object *obedit, @@ -2225,12 +2082,12 @@ Mesh *editbmesh_get_eval_cage(struct Depsgraph *depsgraph, */ object_get_datamask(depsgraph, obedit, &cddata_masks, nullptr); - if (!em->mesh_eval_cage || - !CustomData_MeshMasks_are_matching(&(em->lastDataMask), &cddata_masks)) { + if (!obedit->runtime.editmesh_eval_cage || + !CustomData_MeshMasks_are_matching(&(obedit->runtime.last_data_mask), &cddata_masks)) { editbmesh_build_data(depsgraph, scene, obedit, em, &cddata_masks); } - return em->mesh_eval_cage; + return obedit->runtime.editmesh_eval_cage; } Mesh *editbmesh_get_eval_cage_from_orig(struct Depsgraph *depsgraph, @@ -2256,8 +2113,7 @@ struct MappedUserData { static void make_vertexcos__mapFunc(void *userData, int index, const float co[3], - const float UNUSED(no_f[3]), - const short UNUSED(no_s[3])) + const float UNUSED(no[3])) { MappedUserData *mappedData = (MappedUserData *)userData; @@ -2304,6 +2160,7 @@ void DM_calc_loop_tangents(DerivedMesh *dm, calc_active_tangent, tangent_names, tangent_names_len, + (const float(*)[3])CustomData_get_layer(&dm->vertData, CD_NORMAL), (const float(*)[3])CustomData_get_layer(&dm->polyData, CD_NORMAL), (const float(*)[3])dm->getLoopDataArray(dm, CD_NORMAL), (const float(*)[3])dm->getVertDataArray(dm, CD_ORCO), /* may be nullptr */ @@ -2386,145 +2243,3 @@ static void mesh_init_origspace(Mesh *mesh) BKE_mesh_tessface_clear(mesh); } - -/* derivedmesh info printing function, - * to help track down differences DM output */ - -#ifndef NDEBUG -# include "BLI_dynstr.h" - -static void dm_debug_info_layers(DynStr *dynstr, - DerivedMesh *dm, - CustomData *cd, - void *(*getElemDataArray)(DerivedMesh *, int)) -{ - int type; - - for (type = 0; type < CD_NUMTYPES; type++) { - if (CustomData_has_layer(cd, type)) { - /* 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); - const int pt_size = pt ? (int)(MEM_allocN_len(pt) / size) : 0; - const char *structname; - int structnum; - CustomData_file_write_info(type, &structname, &structnum); - BLI_dynstr_appendf( - dynstr, - " dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n", - name, - structname, - type, - (const void *)pt, - size, - pt_size); - } - } -} - -char *DM_debug_info(DerivedMesh *dm) -{ - DynStr *dynstr = BLI_dynstr_new(); - char *ret; - const char *tstr; - - BLI_dynstr_append(dynstr, "{\n"); - BLI_dynstr_appendf(dynstr, " 'ptr': '%p',\n", (void *)dm); - switch (dm->type) { - case DM_TYPE_CDDM: - tstr = "DM_TYPE_CDDM"; - break; - case DM_TYPE_CCGDM: - tstr = "DM_TYPE_CCGDM"; - break; - default: - tstr = "UNKNOWN"; - break; - } - BLI_dynstr_appendf(dynstr, " 'type': '%s',\n", tstr); - BLI_dynstr_appendf(dynstr, " 'numVertData': %d,\n", dm->numVertData); - BLI_dynstr_appendf(dynstr, " 'numEdgeData': %d,\n", dm->numEdgeData); - BLI_dynstr_appendf(dynstr, " 'numTessFaceData': %d,\n", dm->numTessFaceData); - BLI_dynstr_appendf(dynstr, " 'numPolyData': %d,\n", dm->numPolyData); - BLI_dynstr_appendf(dynstr, " 'deformedOnly': %d,\n", dm->deformedOnly); - - BLI_dynstr_append(dynstr, " 'vertexLayers': (\n"); - dm_debug_info_layers(dynstr, dm, &dm->vertData, dm->getVertDataArray); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'edgeLayers': (\n"); - dm_debug_info_layers(dynstr, dm, &dm->edgeData, dm->getEdgeDataArray); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'loopLayers': (\n"); - dm_debug_info_layers(dynstr, dm, &dm->loopData, dm->getLoopDataArray); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'polyLayers': (\n"); - dm_debug_info_layers(dynstr, dm, &dm->polyData, dm->getPolyDataArray); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'tessFaceLayers': (\n"); - dm_debug_info_layers(dynstr, dm, &dm->faceData, dm->getTessFaceDataArray); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, "}\n"); - - ret = BLI_dynstr_get_cstring(dynstr); - BLI_dynstr_free(dynstr); - return ret; -} - -void DM_debug_print(DerivedMesh *dm) -{ - char *str = DM_debug_info(dm); - puts(str); - fflush(stdout); - MEM_freeN(str); -} - -bool DM_is_valid(DerivedMesh *dm) -{ - const bool do_verbose = true; - const bool do_fixes = false; - - bool is_valid = true; - bool changed = true; - - is_valid &= BKE_mesh_validate_all_customdata( - dm->getVertDataLayout(dm), - dm->getNumVerts(dm), - dm->getEdgeDataLayout(dm), - dm->getNumEdges(dm), - dm->getLoopDataLayout(dm), - dm->getNumLoops(dm), - dm->getPolyDataLayout(dm), - dm->getNumPolys(dm), - false, /* setting mask here isn't useful, gives false positives */ - do_verbose, - do_fixes, - &changed); - - is_valid &= BKE_mesh_validate_arrays(nullptr, - dm->getVertArray(dm), - dm->getNumVerts(dm), - dm->getEdgeArray(dm), - dm->getNumEdges(dm), - dm->getTessFaceArray(dm), - dm->getNumTessFaces(dm), - dm->getLoopArray(dm), - dm->getNumLoops(dm), - dm->getPolyArray(dm), - dm->getNumPolys(dm), - (MDeformVert *)dm->getVertDataArray(dm, CD_MDEFORMVERT), - do_verbose, - do_fixes, - &changed); - - BLI_assert(changed == false); - - return is_valid; -} - -#endif /* NDEBUG */ diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 16d269f9e26..fde42304185 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -51,6 +51,7 @@ #include "BKE_anim_visualization.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_asset.h" #include "BKE_constraint.h" #include "BKE_deform.h" #include "BKE_fcurve.h" @@ -175,11 +176,11 @@ 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); } } @@ -286,6 +287,30 @@ static void action_blend_read_expand(BlendExpander *expander, ID *id) } } +static IDProperty *action_asset_type_property(const bAction *action) +{ + const bool is_single_frame = BKE_action_has_single_frame(action); + + IDPropertyTemplate idprop = {0}; + idprop.i = is_single_frame; + + IDProperty *property = IDP_New(IDP_INT, &idprop, "is_single_frame"); + return property; +} + +static void action_asset_pre_save(void *asset_ptr, struct AssetMetaData *asset_data) +{ + bAction *action = (bAction *)asset_ptr; + BLI_assert(GS(action->id.name) == ID_AC); + + IDProperty *action_type = action_asset_type_property(action); + BKE_asset_metadata_idprop_ensure(asset_data, action_type); +} + +static AssetTypeInfo AssetType_AC = { + /* pre_save_fn */ action_asset_pre_save, +}; + IDTypeInfo IDType_ID_AC = { .id_code = ID_AC, .id_filter = FILTER_ID_AC, @@ -295,6 +320,7 @@ IDTypeInfo IDType_ID_AC = { .name_plural = "actions", .translation_context = BLT_I18NCONTEXT_ID_ACTION, .flags = IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = &AssetType_AC, .init_data = NULL, .copy_data = action_copy_data, @@ -302,6 +328,7 @@ IDTypeInfo IDType_ID_AC = { .make_local = NULL, .foreach_id = action_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = action_blend_write, @@ -329,7 +356,6 @@ bAction *BKE_action_add(Main *bmain, const char name[]) /* *************** Action Groups *************** */ -/* Get the active action-group for an Action */ bActionGroup *get_active_actiongroup(bAction *act) { bActionGroup *agrp = NULL; @@ -345,7 +371,6 @@ bActionGroup *get_active_actiongroup(bAction *act) return agrp; } -/* Make the given Action-Group the active one */ void set_active_action_group(bAction *act, bActionGroup *agrp, short select) { bActionGroup *grp; @@ -366,7 +391,6 @@ void set_active_action_group(bAction *act, bActionGroup *agrp, short select) } } -/* Sync colors used for action/bone group with theme settings */ void action_group_colors_sync(bActionGroup *grp, const bActionGroup *ref_grp) { /* Only do color copying if using a custom color (i.e. not default color). */ @@ -397,7 +421,6 @@ void action_group_colors_sync(bActionGroup *grp, const bActionGroup *ref_grp) } } -/* Add a new action group with the given name to the action */ bActionGroup *action_groups_add_new(bAction *act, const char name[]) { bActionGroup *agrp; @@ -423,10 +446,6 @@ bActionGroup *action_groups_add_new(bAction *act, const char name[]) return agrp; } -/* Add given channel into (active) group - * - assumes that channel is not linked to anything anymore - * - always adds at the end of the group - */ void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve) { /* sanity checks */ @@ -495,10 +514,6 @@ void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve) fcurve->grp = agrp; } -/* Reconstruct group channel pointers. - * 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) { /* Sanity check. */ @@ -538,7 +553,6 @@ void BKE_action_groups_reconstruct(bAction *act) BLI_movelisttolist(&act->curves, &ungrouped); } -/* Remove the given channel from all groups */ void action_groups_remove_channel(bAction *act, FCurve *fcu) { /* sanity checks */ @@ -579,7 +593,6 @@ void action_groups_remove_channel(bAction *act, FCurve *fcu) BLI_remlink(&act->curves, fcu); } -/* Find a group with the given name */ bActionGroup *BKE_action_group_find_name(bAction *act, const char name[]) { /* sanity checks */ @@ -591,7 +604,6 @@ bActionGroup *BKE_action_group_find_name(bAction *act, const char name[]) return BLI_findstring(&act->groups, name, offsetof(bActionGroup, name)); } -/* Clear all 'temp' flags on all groups */ void action_groups_clear_tempflags(bAction *act) { bActionGroup *agrp; @@ -614,10 +626,6 @@ void BKE_pose_channel_session_uuid_generate(bPoseChannel *pchan) pchan->runtime.session_uuid = BLI_session_uuid_generate(); } -/** - * Return a pointer to the pose channel of the given name - * from this pose. - */ bPoseChannel *BKE_pose_channel_find_name(const bPose *pose, const char *name) { if (ELEM(NULL, pose, name) || (name[0] == '\0')) { @@ -631,14 +639,6 @@ bPoseChannel *BKE_pose_channel_find_name(const bPose *pose, const char *name) return BLI_findstring(&pose->chanbase, name, offsetof(bPoseChannel, name)); } -/** - * Looks to see if the channel with the given name - * already exists in this pose - if not a new one is - * allocated and initialized. - * - * \note Use with care, not on Armature poses but for temporal ones. - * \note (currently used for action constraints and in rebuild_pose). - */ bPoseChannel *BKE_pose_channel_ensure(bPose *pose, const char *name) { bPoseChannel *chan; @@ -705,13 +705,12 @@ bool BKE_pose_channels_is_valid(const bPose *pose) #endif -/** - * Find the active pose-channel for an object - * (we can't just use pose, as layer info is in armature) - * - * \note #Object, not #bPose is used here, as we need layer info from Armature. - */ -bPoseChannel *BKE_pose_channel_active(Object *ob) +bool BKE_pose_is_layer_visible(const bArmature *arm, const bPoseChannel *pchan) +{ + return (pchan->bone->layer & arm->layer); +} + +bPoseChannel *BKE_pose_channel_active(Object *ob, const bool check_arm_layer) { bArmature *arm = (ob) ? ob->data : NULL; bPoseChannel *pchan; @@ -722,23 +721,21 @@ bPoseChannel *BKE_pose_channel_active(Object *ob) /* find active */ for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { - if ((pchan->bone) && (pchan->bone == arm->act_bone) && (pchan->bone->layer & arm->layer)) { - return pchan; + if ((pchan->bone) && (pchan->bone == arm->act_bone)) { + if (!check_arm_layer || BKE_pose_is_layer_visible(arm, pchan)) { + return pchan; + } } } return NULL; } -/** - * Use this when detecting the "other selected bone", - * when we have multiple armatures in pose mode. - * - * In this case the active-selected is an obvious choice when finding the target for a - * constraint for eg. however from the users perspective the active pose bone of the - * active object is the _real_ active bone, so any other non-active selected bone - * is a candidate for being the other selected bone, see: T58447. - */ +bPoseChannel *BKE_pose_channel_active_if_layer_visible(struct Object *ob) +{ + return BKE_pose_channel_active(ob, true); +} + bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) { bArmature *arm = (ob) ? ob->data : NULL; @@ -747,7 +744,7 @@ bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) return NULL; } - bPoseChannel *pchan = BKE_pose_channel_active(ob); + bPoseChannel *pchan = BKE_pose_channel_active_if_layer_visible(ob); if (pchan && (pchan->bone->flag & BONE_SELECTED) && PBONE_VISIBLE(arm, pchan->bone)) { return pchan; } @@ -762,9 +759,6 @@ bPoseChannel *BKE_pose_channel_active_or_first_selected(struct Object *ob) return NULL; } -/** - * \see #ED_armature_ebone_get_mirrored (edit-mode, matching function) - */ bPoseChannel *BKE_pose_channel_get_mirrored(const bPose *pose, const char *name) { char name_flip[MAXBONENAME]; @@ -791,12 +785,6 @@ const char *BKE_pose_ikparam_get_name(bPose *pose) return NULL; } -/** - * Allocate a new pose on the heap, and copy the src pose and its channels - * into the new pose. *dst is set to the newly allocated structure, and assumed to be NULL. - * - * \param dst: Should be freed already, makes entire duplicate. - */ void BKE_pose_copy_data_ex(bPose **dst, const bPose *src, const int flag, @@ -948,10 +936,6 @@ bool BKE_pose_channel_in_IK_chain(Object *ob, bPoseChannel *pchan) return pose_channel_in_IK_chain(ob, pchan, 0); } -/** - * Removes the hash for quick lookup of channels, must - * be done when adding/removing channels. - */ void BKE_pose_channels_hash_ensure(bPose *pose) { if (!pose->chanhash) { @@ -987,9 +971,6 @@ static void pose_channels_remove_internal_links(Object *ob, bPoseChannel *unlink } } -/** - * Selectively remove pose channels. - */ void BKE_pose_channels_remove(Object *ob, bool (*filter_fn)(const char *bone_name, void *user_data), void *user_data) @@ -1059,10 +1040,6 @@ void BKE_pose_channels_remove(Object *ob, } } -/** - * Deallocates a pose channel. - * Does not free the pose channel itself. - */ void BKE_pose_channel_free_ex(bPoseChannel *pchan, bool do_id_user) { if (pchan->custom) { @@ -1091,13 +1068,11 @@ void BKE_pose_channel_free_ex(bPoseChannel *pchan, bool do_id_user) BKE_pose_channel_runtime_free(&pchan->runtime); } -/** Clears the runtime cache of a pose channel without free. */ void BKE_pose_channel_runtime_reset(bPoseChannel_Runtime *runtime) { memset(runtime, 0, sizeof(*runtime)); } -/* Reset all non-persistent fields. */ void BKE_pose_channel_runtime_reset_on_copy(bPoseChannel_Runtime *runtime) { const SessionUUID uuid = runtime->session_uuid; @@ -1105,13 +1080,11 @@ void BKE_pose_channel_runtime_reset_on_copy(bPoseChannel_Runtime *runtime) runtime->session_uuid = uuid; } -/** Deallocates runtime cache of a pose channel */ void BKE_pose_channel_runtime_free(bPoseChannel_Runtime *runtime) { BKE_pose_channel_free_bbone_cache(runtime); } -/** Deallocates runtime cache of a pose channel's B-Bone shape. */ void BKE_pose_channel_free_bbone_cache(bPoseChannel_Runtime *runtime) { runtime->bbone_segments = 0; @@ -1126,10 +1099,6 @@ void BKE_pose_channel_free(bPoseChannel *pchan) BKE_pose_channel_free_ex(pchan, true); } -/** - * Removes and deallocates all channels from a pose. - * Does not free the pose itself. - */ void BKE_pose_channels_free_ex(bPose *pose, bool do_id_user) { bPoseChannel *pchan; @@ -1176,9 +1145,6 @@ void BKE_pose_free_data(bPose *pose) BKE_pose_free_data_ex(pose, true); } -/** - * Removes and deallocates all data from a pose, and also frees the pose. - */ void BKE_pose_free_ex(bPose *pose, bool do_id_user) { if (pose) { @@ -1193,13 +1159,6 @@ void BKE_pose_free(bPose *pose) BKE_pose_free_ex(pose, true); } -/** - * Copy the internal members of each pose channel including constraints - * and ID-Props, used when duplicating bones in editmode. - * (unlike copy_pose_channel_data which only does posing-related stuff). - * - * \note use when copying bones in editmode (on returned value from #BKE_pose_channel_ensure) - */ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_from) { /* copy transform locks */ @@ -1211,7 +1170,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); @@ -1249,10 +1208,6 @@ void BKE_pose_channel_copy_data(bPoseChannel *pchan, const bPoseChannel *pchan_f pchan->drawflag = pchan_from->drawflag; } -/* checks for IK constraint, Spline IK, and also for Follow-Path constraint. - * can do more constraints flags later - */ -/* pose should be entirely OK */ void BKE_pose_update_constraint_flags(bPose *pose) { bPoseChannel *pchan, *parchan; @@ -1327,7 +1282,6 @@ void BKE_pose_tag_update_constraint_flags(bPose *pose) /* ************************** Bone Groups ************************** */ -/* Adds a new bone-group (name may be NULL) */ bActionGroup *BKE_pose_add_group(bPose *pose, const char *name) { bActionGroup *grp; @@ -1346,8 +1300,6 @@ bActionGroup *BKE_pose_add_group(bPose *pose, const char *name) return grp; } -/* Remove the given bone-group (expects 'virtual' index (+1 one, used by active_group etc.)) - * index might be invalid ( < 1), in which case it will be find from grp. */ void BKE_pose_remove_group(bPose *pose, bActionGroup *grp, const int index) { bPoseChannel *pchan; @@ -1386,7 +1338,6 @@ void BKE_pose_remove_group(bPose *pose, bActionGroup *grp, const int index) } } -/* Remove the indexed bone-group (expects 'virtual' index (+1 one, used by active_group etc.)) */ void BKE_pose_remove_group_index(bPose *pose, const int index) { bActionGroup *grp = NULL; @@ -1400,7 +1351,6 @@ void BKE_pose_remove_group_index(bPose *pose, const int index) /* ************** F-Curve Utilities for Actions ****************** */ -/* Check if the given action has any keyframes */ bool action_has_motion(const bAction *act) { FCurve *fcu; @@ -1418,7 +1368,47 @@ bool action_has_motion(const bAction *act) return false; } -/* Calculate the extents of given action */ +bool BKE_action_has_single_frame(const struct bAction *act) +{ + if (act == NULL || BLI_listbase_is_empty(&act->curves)) { + return false; + } + + bool found_key = false; + float found_key_frame = 0.0f; + + LISTBASE_FOREACH (FCurve *, fcu, &act->curves) { + switch (fcu->totvert) { + case 0: + /* No keys, so impossible to come to a conclusion on this curve alone. */ + continue; + case 1: + /* Single key, which is the complex case, so handle below. */ + break; + default: + /* Multiple keys, so there is animation. */ + return false; + } + + const float this_key_frame = fcu->bezt != NULL ? fcu->bezt[0].vec[1][0] : fcu->fpt[0].vec[0]; + if (!found_key) { + found_key = true; + found_key_frame = this_key_frame; + continue; + } + + /* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise + * with these comparisons. */ + if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) { + /* This key differs from the already-found key, so this Action represents animation. */ + return false; + } + } + + /* There is only a single frame if we found at least one key. */ + return found_key; +} + void calc_action_range(const bAction *act, float *start, float *end, short incl_modifiers) { FCurve *fcu; @@ -1506,9 +1496,27 @@ void calc_action_range(const bAction *act, float *start, float *end, short incl_ } } -/* Return flags indicating which transforms the given object/posechannel has - * - if 'curves' is provided, a list of links to these curves are also returned - */ +void BKE_action_get_frame_range(const struct bAction *act, float *r_start, float *r_end) +{ + if (act && (act->flag & ACT_FRAME_RANGE)) { + *r_start = act->frame_start; + *r_end = act->frame_end; + } + else { + calc_action_range(act, r_start, r_end, false); + } + + /* Ensure that action is at least 1 frame long (for NLA strips to have a valid length). */ + if (*r_start >= *r_end) { + *r_end = *r_start + 1.0f; + } +} + +bool BKE_action_is_cyclic(const struct bAction *act) +{ + return act && (act->flag & ACT_FRAME_RANGE) && (act->flag & ACT_CYCLIC); +} + short action_get_item_transforms(bAction *act, Object *ob, bPoseChannel *pchan, ListBase *curves) { PointerRNA ptr; @@ -1639,9 +1647,6 @@ short action_get_item_transforms(bAction *act, Object *ob, bPoseChannel *pchan, /* ************** Pose Management Tools ****************** */ -/** - * Zero the pose transforms for the entire pose or only for selected bones. - */ void BKE_pose_rest(bPose *pose, bool selected_bones_only) { bPoseChannel *pchan; @@ -1706,7 +1711,6 @@ void BKE_pose_copy_pchan_result(bPoseChannel *pchanto, const bPoseChannel *pchan pchanto->protectflag = pchanfrom->protectflag; } -/* both poses should be in sync */ bool BKE_pose_copy_result(bPose *to, bPose *from) { bPoseChannel *pchanto, *pchanfrom; @@ -1731,7 +1735,6 @@ bool BKE_pose_copy_result(bPose *to, bPose *from) return true; } -/* Tag pose for recalc. Also tag all related data to be recalc. */ void BKE_pose_tag_recalc(Main *bmain, bPose *pose) { pose->flag |= POSE_RECALC; @@ -1741,9 +1744,6 @@ void BKE_pose_tag_recalc(Main *bmain, bPose *pose) DEG_relations_tag_update(bmain); } -/* For the calculation of the effects of an Action at the given frame on an object - * This is currently only used for the Action Constraint - */ void what_does_obaction(Object *ob, Object *workob, bPose *pose, diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c index 48472dfc9b3..cc3a15aa546 100644 --- a/source/blender/blenkernel/intern/action_mirror.c +++ b/source/blender/blenkernel/intern/action_mirror.c @@ -327,10 +327,10 @@ static void action_flip_pchan(Object *ob_arm, * 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[4] = {1.0f, 0.0f, 0.0f, 0.0f}; - const bool is_problematic = pchan_flip == NULL && - fabsf(dot_v4v4(pchan->bone->arm_mat[0], unit_x)) <= 1e-6; - if (is_problematic) { + 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}, diff --git a/source/blender/blenkernel/intern/action_test.cc b/source/blender/blenkernel/intern/action_test.cc index c02eca966ad..8423bc923f3 100644 --- a/source/blender/blenkernel/intern/action_test.cc +++ b/source/blender/blenkernel/intern/action_test.cc @@ -24,6 +24,8 @@ #include "BLI_listbase.h" +#include "MEM_guardedalloc.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -141,4 +143,97 @@ TEST(action_groups, ReconstructGroupsWithReordering) EXPECT_EQ(groupDcurve2.next, nullptr); } +namespace { + +/* Allocate fcu->bezt, and also return a unique_ptr to it for easily freeing the memory. */ +std::unique_ptr<BezTriple[]> allocate_keyframes(FCurve *fcu, const size_t num_keyframes) +{ + auto bezt_uptr = std::make_unique<BezTriple[]>(num_keyframes); + fcu->bezt = bezt_uptr.get(); + return bezt_uptr; +} + +/* Append keyframe, assumes that fcu->bezt is allocated and has enough space. */ +void add_keyframe(FCurve *fcu, float x, float y) +{ + /* The insert_keyframe functions are in the editors, so we cannot link to those here. */ + BezTriple the_keyframe; + memset(&the_keyframe, 0, sizeof(the_keyframe)); + + /* Copied from insert_vert_fcurve() in keyframing.c. */ + the_keyframe.vec[0][0] = x - 1.0f; + the_keyframe.vec[0][1] = y; + the_keyframe.vec[1][0] = x; + the_keyframe.vec[1][1] = y; + the_keyframe.vec[2][0] = x + 1.0f; + the_keyframe.vec[2][1] = y; + + memcpy(&fcu->bezt[fcu->totvert], &the_keyframe, sizeof(the_keyframe)); + fcu->totvert++; +} + +} // namespace + +TEST(action_assets, BKE_action_has_single_frame) +{ + /* NULL action. */ + EXPECT_FALSE(BKE_action_has_single_frame(nullptr)) << "NULL Action cannot have a single frame."; + + /* No FCurves. */ + { + const bAction empty = {{nullptr}}; + EXPECT_FALSE(BKE_action_has_single_frame(&empty)) + << "Action without FCurves cannot have a single frame."; + } + + /* One curve with one key. */ + { + FCurve fcu = {nullptr}; + std::unique_ptr<BezTriple[]> bezt = allocate_keyframes(&fcu, 1); + add_keyframe(&fcu, 1.0f, 2.0f); + + bAction action = {{nullptr}}; + BLI_addtail(&action.curves, &fcu); + + EXPECT_TRUE(BKE_action_has_single_frame(&action)) + << "Action with one FCurve and one key should have single frame."; + } + + /* Two curves with one key each. */ + { + FCurve fcu1 = {nullptr}; + FCurve fcu2 = {nullptr}; + std::unique_ptr<BezTriple[]> bezt1 = allocate_keyframes(&fcu1, 1); + std::unique_ptr<BezTriple[]> bezt2 = allocate_keyframes(&fcu2, 1); + add_keyframe(&fcu1, 1.0f, 327.0f); + add_keyframe(&fcu2, 1.0f, 47.0f); /* Same X-coordinate as the other one. */ + + bAction action = {{nullptr}}; + BLI_addtail(&action.curves, &fcu1); + BLI_addtail(&action.curves, &fcu2); + + EXPECT_TRUE(BKE_action_has_single_frame(&action)) + << "Two FCurves with keys on the same frame should have single frame."; + + /* Modify the 2nd curve so it's keyed on a different frame. */ + fcu2.bezt[0].vec[1][0] = 2.0f; + EXPECT_FALSE(BKE_action_has_single_frame(&action)) + << "Two FCurves with keys on different frames should have animation."; + } + + /* One curve with two keys. */ + { + FCurve fcu = {nullptr}; + std::unique_ptr<BezTriple[]> bezt = allocate_keyframes(&fcu, 2); + add_keyframe(&fcu, 1.0f, 2.0f); + add_keyframe(&fcu, 2.0f, 2.5f); + + bAction action = {{nullptr}}; + BLI_addtail(&action.curves, &fcu); + + EXPECT_FALSE(BKE_action_has_single_frame(&action)) + << "Action with one FCurve and two keys must have animation."; + } +} + } // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/anim_data.c b/source/blender/blenkernel/intern/anim_data.c index 7e4ab754500..42b72a7cd66 100644 --- a/source/blender/blenkernel/intern/anim_data.c +++ b/source/blender/blenkernel/intern/anim_data.c @@ -69,7 +69,6 @@ static CLG_LogRef LOG = {"bke.anim_sys"}; /* Getter/Setter -------------------------------------------- */ -/* Check if ID can have AnimData */ bool id_type_can_have_animdata(const short id_type) { const IDTypeInfo *typeinfo = BKE_idtype_get_info_from_idcode(id_type); @@ -89,9 +88,6 @@ bool id_can_have_animdata(const ID *id) return id_type_can_have_animdata(GS(id->name)); } -/** - * Get #AnimData from the given ID-block. - */ AnimData *BKE_animdata_from_id(ID *id) { /* In order for this to work, we assume that the #AnimData pointer is stored @@ -106,9 +102,6 @@ AnimData *BKE_animdata_from_id(ID *id) return NULL; } -/** - * Ensure #AnimData exists in the given ID-block (when supported). - */ AnimData *BKE_animdata_ensure_id(ID *id) { /* In order for this to work, we assume that the #AnimData pointer is stored @@ -137,16 +130,6 @@ AnimData *BKE_animdata_ensure_id(ID *id) /* Action Setter --------------------------------------- */ -/** - * Called when user tries to change the active action of an #AnimData block - * (via RNA, Outliner, etc.) - * - * \param reports: Can be NULL. - * \param id: The owner of the animation data - * \param act: The Action to set, or NULL to clear. - * - * \return true when the action was successfully updated, false otherwise. - */ bool BKE_animdata_set_action(ReportList *reports, ID *id, bAction *act) { AnimData *adt = BKE_animdata_from_id(id); @@ -226,7 +209,6 @@ bool BKE_animdata_action_ensure_idroot(const ID *owner, bAction *action) /* Freeing -------------------------------------------- */ -/* Free AnimData used by the nominated ID-block, and clear ID-block's AnimData pointer */ void BKE_animdata_free(ID *id, const bool do_id_user) { /* Only some ID-blocks have this info for now, so we cast the @@ -287,18 +269,14 @@ bool BKE_animdata_id_is_animated(const struct ID *id) !BLI_listbase_is_empty(&adt->overrides); } -/** - * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). - */ 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) { @@ -309,12 +287,6 @@ void BKE_animdata_foreach_id(AnimData *adt, LibraryForeachIDData *data) /* Copying -------------------------------------------- */ -/** - * Make a copy of the given AnimData - to be used when copying data-blocks. - * \param flag: Control ID pointers management, - * see LIB_ID_CREATE_.../LIB_ID_COPY_... flags in BKE_lib_id.h - * \return The copied animdata. - */ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag) { AnimData *dadt; @@ -367,11 +339,6 @@ AnimData *BKE_animdata_copy(Main *bmain, AnimData *adt, const int flag) return dadt; } -/** - * \param flag: Control ID pointers management, - * see LIB_ID_CREATE_.../LIB_ID_COPY_... flags in BKE_lib_id.h - * \return true is successfully copied. - */ bool BKE_animdata_copy_id(Main *bmain, ID *id_to, ID *id_from, const int flag) { AnimData *adt; @@ -432,7 +399,6 @@ void BKE_animdata_duplicate_id_action(struct Main *bmain, } } -/* Merge copies of the data from the src AnimData into the destination AnimData */ void BKE_animdata_merge_copy( Main *bmain, ID *dst_id, ID *src_id, eAnimData_MergeCopy_Modes action_mode, bool fix_drivers) { @@ -647,12 +613,6 @@ static void animdata_move_drivers_by_basepath(AnimData *srcAdt, } } -/* Transfer the animation data from srcID to dstID where the srcID - * animation data is based off "basepath", creating new AnimData and - * associated data as necessary. - * - * basepaths is a list of AnimationBasePathChange. - */ void BKE_animdata_transfer_by_basepath(Main *bmain, ID *srcID, ID *dstID, ListBase *basepaths) { AnimData *srcAdt = NULL, *dstAdt = NULL; @@ -716,52 +676,6 @@ void BKE_animdata_transfer_by_basepath(Main *bmain, ID *srcID, ID *dstID, ListBa } } -/** - * Temporary wrapper for driver operators for buttons to make it easier to create - * such drivers by rerouting all paths through the active object instead so that - * they will get picked up by the dependency system. - * - * \param C: Context pointer - for getting active data - * \param[in,out] ptr: RNA pointer for property's data-block. - * May be modified as result of path remapping. - * \param prop: RNA definition of property to add for - * \return MEM_alloc'd string representing the path to the property from the given #PointerRNA - */ -char *BKE_animdata_driver_path_hack(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - char *base_path) -{ - ID *id = ptr->owner_id; - ScrArea *area = CTX_wm_area(C); - - /* get standard path which may be extended */ - char *basepath = base_path ? base_path : RNA_path_from_ID_to_property(ptr, prop); - char *path = basepath; /* in case no remapping is needed */ - - /* Remapping will only be performed in the Properties Editor, as only this - * restricts the subspace of options to the 'active' data (a manageable state) - */ - /* TODO: watch out for pinned context? */ - if ((area) && (area->spacetype == SPACE_PROPERTIES)) { - Object *ob = CTX_data_active_object(C); - - if (ob && id) { - /* TODO: after material textures were removed, this function serves - * no purpose anymore, but could be used again so was not removed. */ - - /* fix RNA pointer, as we've now changed the ID root by changing the paths */ - if (basepath != path) { - /* rebase provided pointer so that it starts from object... */ - RNA_pointer_create(&ob->id, ptr->type, ptr->data, ptr); - } - } - } - - /* the path should now have been corrected for use */ - return path; -} - /* Path Validation -------------------------------------------- */ /* Check if a given RNA Path is valid, by tracing it from the given ID, @@ -956,14 +870,6 @@ static bool nlastrips_path_rename_fix(ID *owner_id, /* Rename Sub-ID Entities in RNA Paths ----------------------- */ -/* Fix up the given RNA-Path - * - * This is just an external wrapper for the RNA-Path fixing function, - * with input validity checks on top of the basic method. - * - * NOTE: it is assumed that the structure we're replacing is <prefix><["><name><"]> - * i.e. pose.bones["Bone"] - */ char *BKE_animsys_fix_rna_path_rename(ID *owner_id, char *old_path, const char *prefix, @@ -1019,14 +925,6 @@ char *BKE_animsys_fix_rna_path_rename(ID *owner_id, return result; } -/* Fix all RNA_Paths in the given Action, relative to the given ID block - * - * This is just an external wrapper for the F-Curve fixing function, - * with input validity checks on top of the basic method. - * - * NOTE: it is assumed that the structure we're replacing is <prefix><["><name><"]> - * i.e. pose.bones["Bone"] - */ void BKE_action_fix_paths_rename(ID *owner_id, bAction *act, const char *prefix, @@ -1070,10 +968,6 @@ void BKE_action_fix_paths_rename(ID *owner_id, MEM_freeN(newN); } -/* Fix all RNA-Paths in the AnimData block used by the given ID block - * NOTE: it is assumed that the structure we're replacing is <prefix><["><name><"]> - * i.e. pose.bones["Bone"] - */ void BKE_animdata_fix_paths_rename(ID *owner_id, AnimData *adt, ID *ref_id, @@ -1282,7 +1176,6 @@ void BKE_fcurves_id_cb(ID *id, ID_FCurve_Edit_Callback func, void *user_data) } } -/* apply the given callback function on all F-Curves attached to data in main database */ void BKE_fcurves_main_cb(Main *bmain, ID_FCurve_Edit_Callback func, void *user_data) { /* Wrap F-Curve operation stuff to pass to the general AnimData-level func */ @@ -1294,7 +1187,6 @@ void BKE_fcurves_main_cb(Main *bmain, ID_FCurve_Edit_Callback func, void *user_d /* Whole Database Ops -------------------------------------------- */ -/* apply the given callback function on all data in main database */ void BKE_animdata_main_cb(Main *bmain, ID_AnimData_Edit_Callback func, void *user_data) { ID *id; @@ -1405,10 +1297,6 @@ void BKE_animdata_main_cb(Main *bmain, ID_AnimData_Edit_Callback func, void *use ANIMDATA_IDS_CB(bmain->simulations.first); } -/* Fix all RNA-Paths throughout the database (directly access the Global.main version) - * NOTE: it is assumed that the structure we're replacing is <prefix><["><name><"]> - * i.e. pose.bones["Bone"] - */ void BKE_animdata_fix_paths_rename_all(ID *ref_id, const char *prefix, const char *oldName, @@ -1418,11 +1306,6 @@ void BKE_animdata_fix_paths_rename_all(ID *ref_id, BKE_animdata_fix_paths_rename_all_ex(bmain, ref_id, prefix, oldName, newName, 0, 0, 1); } -/* Fix all RNA-Paths throughout the database - * NOTE: it is assumed that the structure we're replacing is <prefix><["><name><"]> - * i.e. pose.bones["Bone"] - */ -/* TODO: use BKE_animdata_main_cb for looping over all data. */ void BKE_animdata_fix_paths_rename_all_ex(Main *bmain, ID *ref_id, const char *prefix, @@ -1432,6 +1315,7 @@ void BKE_animdata_fix_paths_rename_all_ex(Main *bmain, const int newSubscript, const bool verify_paths) { + /* TODO: use BKE_animdata_main_cb for looping over all data. */ ID *id; diff --git a/source/blender/blenkernel/intern/anim_path.c b/source/blender/blenkernel/intern/anim_path.c index de470a15041..43af55e9b6b 100644 --- a/source/blender/blenkernel/intern/anim_path.c +++ b/source/blender/blenkernel/intern/anim_path.c @@ -230,14 +230,6 @@ static bool binary_search_anim_path(const float *accum_len_arr, } } -/** - * Calculate the deformation implied by the curve path at a given parametric position, - * and returns whether this operation succeeded. - * - * \param ctime: Time is normalized range <0-1>. - * - * \return success. - */ bool BKE_where_on_path(const Object *ob, float ctime, float r_vec[4], @@ -254,6 +246,10 @@ bool BKE_where_on_path(const Object *ob, CLOG_WARN(&LOG, "No curve cache!"); return false; } + if (ob->runtime.curve_cache->anim_path_accum_length == NULL) { + CLOG_WARN(&LOG, "No anim path!"); + return false; + } /* We only use the first curve. */ BevList *bl = ob->runtime.curve_cache->bev.first; if (bl == NULL || !bl->nr) { diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 92b0db5b214..b5ea68aaadc 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -84,8 +84,6 @@ static CLG_LogRef LOG = {"bke.anim_sys"}; /* Finding Tools --------------------------- */ -/* Find the first path that matches the given criteria */ -/* TODO: do we want some method to perform partial matches too? */ KS_Path *BKE_keyingset_find_path(KeyingSet *ks, ID *id, const char group_name[], @@ -138,8 +136,6 @@ KS_Path *BKE_keyingset_find_path(KeyingSet *ks, /* Defining Tools --------------------------- */ -/* Used to create a new 'custom' KeyingSet for the user, - * that will be automatically added to the stack */ KeyingSet *BKE_keyingset_add( ListBase *list, const char idname[], const char name[], short flag, short keyingflag) { @@ -174,9 +170,6 @@ KeyingSet *BKE_keyingset_add( return ks; } -/* Add a path to a KeyingSet. Nothing is returned for now... - * Checks are performed to ensure that destination is appropriate for the KeyingSet in question - */ KS_Path *BKE_keyingset_add_path(KeyingSet *ks, ID *id, const char group_name[], @@ -240,7 +233,6 @@ KS_Path *BKE_keyingset_add_path(KeyingSet *ks, return ksp; } -/* Free the given Keying Set path */ void BKE_keyingset_free_path(KeyingSet *ks, KS_Path *ksp) { /* sanity check */ @@ -257,7 +249,6 @@ void BKE_keyingset_free_path(KeyingSet *ks, KS_Path *ksp) BLI_freelinkN(&ks->paths, ksp); } -/* Copy all KeyingSets in the given list */ void BKE_keyingsets_copy(ListBase *newlist, const ListBase *list) { KeyingSet *ksn; @@ -276,7 +267,6 @@ void BKE_keyingsets_copy(ListBase *newlist, const ListBase *list) /* Freeing Tools --------------------------- */ -/* Free data for KeyingSet but not set itself */ void BKE_keyingset_free(KeyingSet *ks) { KS_Path *ksp, *kspn; @@ -293,7 +283,6 @@ void BKE_keyingset_free(KeyingSet *ks) } } -/* Free all the KeyingSets in the given list */ void BKE_keyingsets_free(ListBase *list) { KeyingSet *ks, *ksn; @@ -490,7 +479,6 @@ bool BKE_animsys_read_from_rna_path(PathResolvedRNA *anim_rna, float *r_value) return true; } -/* Write the given value to a setting using RNA, and return success */ bool BKE_animsys_write_to_rna_path(PathResolvedRNA *anim_rna, const float value) { PropertyRNA *prop = anim_rna->prop; @@ -831,7 +819,6 @@ static void action_idcode_patch_check(ID *id, bAction *act) /* ----------------------------------------- */ -/* Evaluate Action Group */ void animsys_evaluate_action_group(PointerRNA *ptr, bAction *act, bActionGroup *agrp, @@ -864,7 +851,6 @@ void animsys_evaluate_action_group(PointerRNA *ptr, } } -/* Evaluate Action (F-Curve Bag) */ void animsys_evaluate_action(PointerRNA *ptr, bAction *act, const AnimationEvalContext *anim_eval_context, @@ -881,7 +867,6 @@ 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, @@ -960,7 +945,6 @@ static void nlastrip_evaluate_controls(NlaStrip *strip, } } -/* gets the strip active at the current time for a list of strips for evaluation purposes */ NlaEvalStrip *nlastrips_ctime_get_strip(ListBase *list, ListBase *strips, short index, @@ -1383,7 +1367,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]; @@ -1391,7 +1375,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]; @@ -2402,7 +2386,6 @@ static void nlastrip_evaluate_meta(PointerRNA *ptr, nlaeval_fmodifiers_split_stacks(&strip->modifiers, modifiers); } -/* evaluates the given evaluation strip */ void nlastrip_evaluate(PointerRNA *ptr, NlaEvalData *channels, ListBase *modifiers, @@ -2447,7 +2430,6 @@ void nlastrip_evaluate(PointerRNA *ptr, strip->flag &= ~NLASTRIP_FLAG_EDIT_TOUCHED; } -/* write the accumulated settings to */ void nladata_flush_channels(PointerRNA *ptr, NlaEvalData *channels, NlaEvalSnapshot *snapshot, @@ -2977,14 +2959,6 @@ void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapsh } } -/** - * Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according - * to the given \a upper_blendmode and \a upper_influence. - * - * For \a upper_snapshot, blending limited to values in the \a blend_domain. - * For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel - * where only a subset of values are blended. - */ void nlasnapshot_blend(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, NlaEvalSnapshot *upper_snapshot, @@ -3012,14 +2986,6 @@ void nlasnapshot_blend(NlaEvalData *eval_data, } } -/** - * Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot. - * - * Only channels that exist within \a blended_snapshot are inverted. - * - * For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions. - * Only values within the \a remap_domain are processed. - */ void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, NlaEvalSnapshot *blended_snapshot, @@ -3050,15 +3016,6 @@ void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, /* ---------------------- */ -/** - * Prepare data necessary to compute correct keyframe values for NLA strips - * with non-Replace mode or influence different from 1. - * - * \param cache: List used to cache contexts for reuse when keying - * multiple channels in one operation. - * \param ptr: RNA pointer to the Object with the animation. - * \return Keyframing context, or NULL if not necessary. - */ NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( struct ListBase *cache, struct PointerRNA *ptr, @@ -3095,18 +3052,6 @@ NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context( return ctx; } -/** - * Apply correction from the NLA context to the values about to be keyframed. - * - * \param context: Context to use (may be NULL). - * \param prop_ptr: Property about to be keyframed. - * \param[in,out] values: Array of property values to adjust. - * \param count: Number of values in the array. - * \param index: Index of the element about to be updated, or -1. - * \param[out] r_force_all: Set to true if all channels must be inserted. May be NULL. - * \return False if correction fails due to a division by zero, - * or null r_force_all when all channels are required. - */ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, struct PointerRNA *prop_ptr, struct PropertyRNA *prop, @@ -3202,9 +3147,6 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, return successful_remap; } -/** - * Free all cached contexts from the list. - */ void BKE_animsys_free_nla_keyframing_context_cache(struct ListBase *cache) { LISTBASE_FOREACH (NlaKeyframingContext *, ctx, cache) { @@ -3270,12 +3212,6 @@ static void animsys_evaluate_overrides(PointerRNA *ptr, AnimData *adt) * However, the code for this is relatively harmless, so is left in the code for now. */ -/* Evaluation loop for evaluation animation data - * - * This assumes that the animation-data provided belongs to the ID block in question, - * and that the flags for which parts of the anim-data settings need to be recalculated - * have been set already by the depsgraph. Now, we use the recalc - */ void BKE_animsys_evaluate_animdata(ID *id, AnimData *adt, const AnimationEvalContext *anim_eval_context, @@ -3329,13 +3265,6 @@ void BKE_animsys_evaluate_animdata(ID *id, animsys_evaluate_overrides(&id_ptr, adt); } -/* Evaluation of all ID-blocks with Animation Data blocks - Animation Data Only - * - * This will evaluate only the animation info available in the animation data-blocks - * encountered. In order to enforce the system by which some settings controlled by a - * 'local' (i.e. belonging in the nearest ID-block that setting is related to, not a - * standard 'root') block are overridden by a larger 'user' - */ void BKE_animsys_evaluate_all_animation(Main *main, Depsgraph *depsgraph, float ctime) { ID *id; diff --git a/source/blender/blenkernel/intern/anim_visualization.c b/source/blender/blenkernel/intern/anim_visualization.c index 56bd8e769bc..fdea52bcd64 100644 --- a/source/blender/blenkernel/intern/anim_visualization.c +++ b/source/blender/blenkernel/intern/anim_visualization.c @@ -39,7 +39,6 @@ /* ******************************************************************** */ /* Animation Visualization */ -/* Initialize the default settings for animation visualization */ void animviz_settings_init(bAnimVizSettings *avs) { /* sanity check */ @@ -62,7 +61,6 @@ void animviz_settings_init(bAnimVizSettings *avs) /* ------------------- */ -/* Free the given motion path's cache */ void animviz_free_motionpath_cache(bMotionPath *mpath) { /* sanity check */ @@ -84,9 +82,6 @@ void animviz_free_motionpath_cache(bMotionPath *mpath) mpath->length = 0; } -/* Free the given motion path instance and its data - * NOTE: this frees the motion path given! - */ void animviz_free_motionpath(bMotionPath *mpath) { /* sanity check */ @@ -103,7 +98,6 @@ void animviz_free_motionpath(bMotionPath *mpath) /* ------------------- */ -/* Make a copy of motionpath data, so that viewing with copy on write works */ bMotionPath *animviz_copy_motionpath(const bMotionPath *mpath_src) { bMotionPath *mpath_dst; @@ -125,14 +119,6 @@ bMotionPath *animviz_copy_motionpath(const bMotionPath *mpath_src) /* ------------------- */ -/** - * Setup motion paths for the given data. - * \note Only used when explicitly calculating paths on bones which may/may not be consider already - * - * \param scene: Current scene (for frame ranges, etc.) - * \param ob: Object to add paths for (must be provided) - * \param pchan: Posechannel to add paths for (optional; if not provided, object-paths are assumed) - */ bMotionPath *animviz_verify_motionpaths(ReportList *reports, Scene *scene, Object *ob, diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc index 67611053d83..22c2f83e8be 100644 --- a/source/blender/blenkernel/intern/anonymous_attribute.cc +++ b/source/blender/blenkernel/intern/anonymous_attribute.cc @@ -97,6 +97,7 @@ void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anony { 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; } } diff --git a/source/blender/blenkernel/intern/appdir.c b/source/blender/blenkernel/intern/appdir.c index eae331fc7d1..9dd4c7e503a 100644 --- a/source/blender/blenkernel/intern/appdir.c +++ b/source/blender/blenkernel/intern/appdir.c @@ -99,15 +99,6 @@ static bool is_appdir_init = false; # define ASSERT_IS_INIT() ((void)0) #endif -/** - * Sanity check to ensure correct API use in debug mode. - * - * Run this once the first level of arguments has been passed so we can be sure - * `--env-system-datafiles`, and other `--env-*` arguments has been passed. - * - * Without this any callers to this module that run early on, - * will miss out on changes from parsing arguments. - */ void BKE_appdir_init(void) { #ifndef NDEBUG @@ -147,13 +138,6 @@ static char *blender_version_decimal(const int version) /** \name Default Directories * \{ */ -/** - * 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. - * - * \note On Windows `Users/{MyUserName}/Documents` is used as it's the default location to save - * documents. - */ const char *BKE_appdir_folder_default(void) { #ifndef WIN32 @@ -169,24 +153,37 @@ const char *BKE_appdir_folder_default(void) #endif /* WIN32 */ } -/** - * Get the user's home directory, i.e. $HOME on UNIX, %userprofile% on Windows. - */ -const char *BKE_appdir_folder_home(void) +const char *BKE_appdir_folder_root(void) { #ifndef WIN32 - return BLI_getenv("HOME"); -#else /* Windows */ + 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; +} + +const char *BKE_appdir_folder_home(void) +{ +#ifdef WIN32 return BLI_getenv("userprofile"); +#elif defined(__APPLE__) + return BLI_expand_tilde("~/"); +#else + return BLI_getenv("HOME"); #endif } -/** - * 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. - * - * \returns True if the path is valid and points to an existing directory. - */ bool BKE_appdir_folder_documents(char *dir) { dir[0] = '\0'; @@ -217,26 +214,53 @@ bool BKE_appdir_folder_documents(char *dir) 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_folder_caches(char *r_path, const size_t path_len) { - bool success = false; + 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; +} + +bool BKE_appdir_font_folder_default(char *dir) +{ + 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__) + STRNCPY(test_dir, BLI_expand_tilde("~/Library/Fonts/")); + BLI_path_slash_ensure(test_dir); +#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; } /** \} */ @@ -391,10 +415,6 @@ static bool get_path_local(char *targetpath, targetpath, targetpath_len, folder_name, subfolder_name, version, check_is_dir); } -/** - * Check if this is an install with user files kept together - * with the Blender executable and its installation files. - */ bool BKE_appdir_app_is_portable_install(void) { /* Detect portable install by the existence of `config` folder. */ @@ -559,13 +579,6 @@ static bool get_path_system(char *targetpath, /** \name Path Presets API * \{ */ -/** - * Get a folder out of the \a folder_id presets for paths. - * - * \param subfolder: The name of a directory to check for, - * this may contain path separators but must resolve to a directory, checked with #BLI_is_dir. - * \return The path if found, NULL string if not. - */ bool BKE_appdir_folder_id_ex(const int folder_id, const char *subfolder, char *path, @@ -679,9 +692,6 @@ const char *BKE_appdir_folder_id(const int folder_id, const char *subfolder) return NULL; } -/** - * Returns the path to a folder in the user area without checking that it actually exists first. - */ const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder) { const int version = BLENDER_VERSION; @@ -728,9 +738,6 @@ const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *su return path; } -/** - * Returns the path to a folder in the user area, creating it if it doesn't exist. - */ const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder) { const char *path; @@ -756,10 +763,6 @@ const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfold return path; } -/** - * Returns the path of the top-level version-specific local, user or system directory. - * If check_is_dir, then the result will be NULL if the directory doesn't exist. - */ const char *BKE_appdir_folder_id_version(const int folder_id, const int version, const bool check_is_dir) @@ -875,18 +878,12 @@ void BKE_appdir_program_path_init(const char *argv0) BLI_split_dir_part(g_app.program_filename, g_app.program_dirname, sizeof(g_app.program_dirname)); } -/** - * Path to executable - */ const char *BKE_appdir_program_path(void) { BLI_assert(g_app.program_filename[0]); return g_app.program_filename; } -/** - * Path to directory of executable - */ const char *BKE_appdir_program_dir(void) { BLI_assert(g_app.program_dirname[0]); @@ -980,9 +977,6 @@ static const int app_template_directory_id[2] = { BLENDER_SYSTEM_SCRIPTS, }; -/** - * Return true if templates exist - */ bool BKE_appdir_app_template_any(void) { char temp_dir[FILE_MAX]; @@ -1150,14 +1144,13 @@ static void tempdir_session_create(char *tempdir_session, BLI_strncpy(tempdir_session, tempdir, tempdir_session_len); } -/** - * Sets #g_app.temp_dirname_base to \a userdir if specified and is a valid directory, - * otherwise chooses a suitable OS-specific temporary directory. - * Sets #g_app.temp_dirname_session to a #mkdtemp - * generated sub-dir of #g_app.temp_dirname_base. - */ void BKE_tempdir_init(const char *userdir) { + /* Sets #g_app.temp_dirname_base to \a userdir if specified and is a valid directory, + * otherwise chooses a suitable OS-specific temporary directory. + * Sets #g_app.temp_dirname_session to a #mkdtemp + * generated sub-dir of #g_app.temp_dirname_base. */ + where_is_temp(g_app.temp_dirname_base, sizeof(g_app.temp_dirname_base), userdir); /* Clear existing temp dir, if needed. */ @@ -1167,25 +1160,16 @@ void BKE_tempdir_init(const char *userdir) g_app.temp_dirname_session, sizeof(g_app.temp_dirname_session), g_app.temp_dirname_base); } -/** - * Path to temporary directory (with trailing slash) - */ const char *BKE_tempdir_session(void) { return g_app.temp_dirname_session[0] ? g_app.temp_dirname_session : BKE_tempdir_base(); } -/** - * Path to persistent temporary directory (with trailing slash) - */ const char *BKE_tempdir_base(void) { return g_app.temp_dirname_base; } -/** - * Delete content of this instance's temp dir. - */ void BKE_tempdir_session_purge(void) { if (g_app.temp_dirname_session[0] && BLI_is_dir(g_app.temp_dirname_session)) { diff --git a/source/blender/blenkernel/intern/armature.c b/source/blender/blenkernel/intern/armature.c index a86f436185e..5704ef6e42f 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)); } } } @@ -316,6 +322,7 @@ IDTypeInfo IDType_ID_AR = { .name_plural = "armatures", .translation_context = BLT_I18NCONTEXT_ID_ARMATURE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = armature_init_data, .copy_data = armature_copy_data, @@ -323,6 +330,7 @@ IDTypeInfo IDType_ID_AR = { .make_local = NULL, .foreach_id = armature_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = armature_blend_write, @@ -601,10 +609,6 @@ static Bone *get_named_bone_bonechildren(ListBase *lb, const char *name) return NULL; } -/** - * Walk the list until the bone is found (slow!), - * use #BKE_armature_bone_from_name_map for multiple lookups. - */ Bone *BKE_armature_find_bone_name(bArmature *arm, const char *name) { if (!arm) { @@ -709,10 +713,6 @@ void BKE_armature_refresh_layer_used(struct Depsgraph *depsgraph, struct bArmatu /** \name Armature Layer Refresh Used * \{ */ -/* Finds the best possible extension to the name on a particular axis. (For renaming, check for - * unique names afterwards) strip_number: removes number extensions (TODO: not used) - * axis: the axis to name on - * head/tail: the head/tail co-ordinate of the bone on the specified axis */ bool bone_autoside_name( char name[MAXBONENAME], int UNUSED(strip_number), short axis, float head, float tail) { @@ -924,7 +924,6 @@ static void evaluate_cubic_bezier(const float control[4][3], madd_v3_v3v3fl(r_pos, layer2[0], r_tangent, t); } -/* Get "next" and "prev" bones - these are used for handle calculations. */ void BKE_pchan_bbone_handles_get(bPoseChannel *pchan, bPoseChannel **r_prev, bPoseChannel **r_next) { if (pchan->bone->bbone_prev_type == BBONE_HANDLE_AUTO) { @@ -951,7 +950,6 @@ void BKE_pchan_bbone_handles_get(bPoseChannel *pchan, bPoseChannel **r_prev, bPo } } -/* Compute B-Bone spline parameters for the given channel. */ void BKE_pchan_bbone_spline_params_get(struct bPoseChannel *pchan, const bool rest, struct BBoneSplineParameters *param) @@ -1197,8 +1195,6 @@ void BKE_pchan_bbone_spline_params_get(struct bPoseChannel *pchan, } } -/* Fills the array with the desired amount of bone->segments elements. - * This calculation is done within unit bone space. */ void BKE_pchan_bbone_spline_setup(bPoseChannel *pchan, const bool rest, const bool for_deform, @@ -1211,7 +1207,6 @@ void BKE_pchan_bbone_spline_setup(bPoseChannel *pchan, pchan->bone->segments = BKE_pchan_bbone_spline_compute(¶m, for_deform, result_array); } -/* Computes the bezier handle vectors and rolls coming from custom handles. */ void BKE_pchan_bbone_handles_compute(const BBoneSplineParameters *param, float h1[3], float *r_roll1, @@ -1361,16 +1356,17 @@ static void ease_handle_axis(const float deriv1[3], const float deriv2[3], float copy_v3_v3(r_axis, deriv1); - float len1 = len_squared_v3(deriv1), len2 = len_squared_v3(deriv2); - float ratio = len1 / len2; - + const float len2 = len_squared_v3(deriv2); + if (UNLIKELY(len2 == 0.0f)) { + return; + } + const float len1 = len_squared_v3(deriv1); + const float ratio = len1 / len2; if (ratio < gap * gap) { madd_v3_v3fl(r_axis, deriv2, gap - sqrtf(ratio)); } } -/* Fills the array with the desired amount of bone->segments elements. - * This calculation is done within unit bone space. */ int BKE_pchan_bbone_spline_compute(BBoneSplineParameters *param, const bool for_deform, Mat4 *result_array) @@ -1495,17 +1491,16 @@ 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"); } } -/** Compute and cache the B-Bone shape in the channel runtime struct. */ void BKE_pchan_bbone_segments_cache_compute(bPoseChannel *pchan) { bPoseChannel_Runtime *runtime = &pchan->runtime; @@ -1557,7 +1552,6 @@ void BKE_pchan_bbone_segments_cache_compute(bPoseChannel *pchan) } } -/** Copy cached B-Bone segments from one channel to another */ void BKE_pchan_bbone_segments_cache_copy(bPoseChannel *pchan, bPoseChannel *pchan_from) { bPoseChannel_Runtime *runtime = &pchan->runtime; @@ -1581,10 +1575,6 @@ void BKE_pchan_bbone_segments_cache_copy(bPoseChannel *pchan, bPoseChannel *pcha } } -/** - * Calculate index and blend factor for the two B-Bone segment nodes - * affecting the point at 0 <= pos <= 1. - */ void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan, float pos, int *r_index, @@ -1616,7 +1606,6 @@ void BKE_pchan_bbone_deform_segment_index(const bPoseChannel *pchan, /** \name Bone Space to Space Conversion API * \{ */ -/* Convert World-Space Matrix to Pose-Space Matrix */ void BKE_armature_mat_world_to_pose(Object *ob, const float inmat[4][4], float outmat[4][4]) { float obmat[4][4]; @@ -1633,9 +1622,6 @@ void BKE_armature_mat_world_to_pose(Object *ob, const float inmat[4][4], float o mul_m4_m4m4(outmat, inmat, obmat); } -/* Convert World-Space Location to Pose-Space Location - * NOTE: this cannot be used to convert to pose-space location of the supplied - * pose-channel into its local space (i.e. 'visual'-keyframing) */ void BKE_armature_loc_world_to_pose(Object *ob, const float inloc[3], float outloc[3]) { float xLocMat[4][4]; @@ -1656,8 +1642,6 @@ void BKE_armature_loc_world_to_pose(Object *ob, const float inloc[3], float outl /** \name Bone Matrix Calculation API * \{ */ -/* Simple helper, computes the offset bone matrix. - * offs_bone = yoffs(b-1) + root(b) + bonemat(b). */ void BKE_bone_offset_matrix_get(const Bone *bone, float offs_bone[4][4]) { BLI_assert(bone->parent != NULL); @@ -1672,24 +1656,6 @@ void BKE_bone_offset_matrix_get(const Bone *bone, float offs_bone[4][4]) offs_bone[3][1] += bone->parent->length; } -/* Construct the matrices (rot/scale and loc) - * to apply the PoseChannels into the armature (object) space. - * I.e. (roughly) the "pose_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b)" in the - * pose_mat(b)= pose_mat(b-1) * yoffs(b-1) * d_root(b) * bone_mat(b) * chan_mat(b) - * ...function. - * - * This allows to get the transformations of a bone in its object space, - * *before* constraints (and IK) get applied (used by pose evaluation code). - * And reverse: to find pchan transformations needed to place a bone at a given loc/rot/scale - * in object space (used by interactive transform, and snapping code). - * - * Note that, with the HINGE/NO_SCALE/NO_LOCAL_LOCATION options, the location matrix - * will differ from the rotation/scale matrix... - * - * NOTE: This cannot be used to convert to pose-space transforms of the supplied - * pose-channel into its local space (i.e. 'visual'-keyframing). - * (note: I don't understand that, so I keep it :p --mont29). - */ void BKE_bone_parent_transform_calc_from_pchan(const bPoseChannel *pchan, BoneParentTransform *r_bpt) { @@ -1719,12 +1685,6 @@ void BKE_bone_parent_transform_calc_from_pchan(const bPoseChannel *pchan, } } -/* Compute the parent transform using data decoupled from specific data structures. - * - * bone_flag: Bone->flag containing settings - * offs_bone: delta from parent to current arm_mat (or just arm_mat if no parent) - * parent_arm_mat, parent_pose_mat: arm_mat and pose_mat of parent, or NULL - * r_bpt: OUTPUT parent transform */ void BKE_bone_parent_transform_calc_from_matrices(int bone_flag, int inherit_scale_mode, const float offs_bone[4][4], @@ -1906,9 +1866,6 @@ void BKE_bone_parent_transform_apply(const struct BoneParentTransform *bpt, rescale_m4(outmat, bpt->post_scale); } -/* Convert Pose-Space Matrix to Bone-Space Matrix. - * NOTE: this cannot be used to convert to pose-space transforms of the supplied - * pose-channel into its local space (i.e. 'visual'-keyframing) */ void BKE_armature_mat_pose_to_bone(bPoseChannel *pchan, const float inmat[4][4], float outmat[4][4]) @@ -1920,7 +1877,6 @@ void BKE_armature_mat_pose_to_bone(bPoseChannel *pchan, BKE_bone_parent_transform_apply(&bpt, inmat, outmat); } -/* Convert Bone-Space Matrix to Pose-Space Matrix. */ void BKE_armature_mat_bone_to_pose(bPoseChannel *pchan, const float inmat[4][4], float outmat[4][4]) @@ -1931,9 +1887,6 @@ void BKE_armature_mat_bone_to_pose(bPoseChannel *pchan, BKE_bone_parent_transform_apply(&bpt, inmat, outmat); } -/* Convert Pose-Space Location to Bone-Space Location - * NOTE: this cannot be used to convert to pose-space location of the supplied - * pose-channel into its local space (i.e. 'visual'-keyframing) */ void BKE_armature_loc_pose_to_bone(bPoseChannel *pchan, const float inloc[3], float outloc[3]) { float xLocMat[4][4]; @@ -1976,9 +1929,6 @@ void BKE_armature_mat_pose_to_bone_ex(struct Depsgraph *depsgraph, BKE_armature_mat_pose_to_bone(&work_pchan, inmat, outmat); } -/** - * Same as #BKE_object_mat3_to_rot(). - */ void BKE_pchan_mat3_to_rot(bPoseChannel *pchan, const float mat[3][3], bool use_compat) { BLI_ASSERT_UNIT_M3(mat); @@ -2001,9 +1951,6 @@ void BKE_pchan_mat3_to_rot(bPoseChannel *pchan, const float mat[3][3], bool use_ } } -/** - * Same as #BKE_object_rot_to_mat3(). - */ void BKE_pchan_rot_to_mat3(const bPoseChannel *pchan, float r_mat[3][3]) { /* rotations may either be quats, eulers (with various rotation orders), or axis-angle */ @@ -2028,10 +1975,6 @@ void BKE_pchan_rot_to_mat3(const bPoseChannel *pchan, float r_mat[3][3]) } } -/** - * Apply a 4x4 matrix to the pose bone, - * similar to #BKE_object_apply_mat4(). - */ void BKE_pchan_apply_mat4(bPoseChannel *pchan, const float mat[4][4], bool use_compat) { float rot[3][3]; @@ -2039,11 +1982,6 @@ void BKE_pchan_apply_mat4(bPoseChannel *pchan, const float mat[4][4], bool use_c BKE_pchan_mat3_to_rot(pchan, rot, use_compat); } -/** - * Remove rest-position effects from pose-transform for obtaining - * 'visual' transformation of pose-channel. - * (used by the Visual-Keyframing stuff). - */ void BKE_armature_mat_pose_to_delta(float delta_mat[4][4], float pose_mat[4][4], float arm_mat[4][4]) @@ -2062,11 +2000,6 @@ void BKE_armature_mat_pose_to_delta(float delta_mat[4][4], * Used for Objects and Pose Channels, since both can have multiple rotation representations. * \{ */ -/** - * Called from RNA when rotation mode changes - * - the result should be that the rotations given in the provided pointers have had conversions - * applied (as appropriate), such that the rotation of the element hasn't 'visually' changed. - */ void BKE_rotMode_change_values( float quat[4], float eul[3], float axis[3], float *angle, short oldMode, short newMode) { @@ -2140,8 +2073,6 @@ void BKE_rotMode_change_values( * * \{ */ -/* Computes vector and roll based on a rotation. - * "mat" must contain only a rotation, and no scaling. */ void mat3_to_vec_roll(const float mat[3][3], float r_vec[3], float *r_roll) { if (r_vec) { @@ -2153,8 +2084,6 @@ void mat3_to_vec_roll(const float mat[3][3], float r_vec[3], float *r_roll) } } -/* Computes roll around the vector that best approximates the matrix. - * If vec is the Y vector from purely rotational mat, result should be exact. */ void mat3_vec_to_roll(const float mat[3][3], const float vec[3], float *r_roll) { float vecmat[3][3], vecmatinv[3][3], rollmat[3][3], q[4]; @@ -2170,98 +2099,105 @@ void mat3_vec_to_roll(const float mat[3][3], const float vec[3], float *r_roll) *r_roll = quat_split_swing_and_twist(q, 1, NULL, NULL); } -/* Calculates the rest matrix of a bone based on its vector and a roll around that vector. */ -/** - * Given `v = (v.x, v.y, v.z)` our (normalized) bone vector, we want the rotation matrix M - * from the Y axis (so that `M * (0, 1, 0) = v`). - * - The rotation axis a lays on XZ plane, and it is orthonormal to v, - * hence to the projection of v onto XZ plane. - * - `a = (v.z, 0, -v.x)` - * - * We know a is eigenvector of M (so M * a = a). - * Finally, we have w, such that M * w = (0, 1, 0) - * (i.e. the vector that will be aligned with Y axis once transformed). - * We know w is symmetric to v by the Y axis. - * - `w = (-v.x, v.y, -v.z)` - * - * Solving this, we get (x, y and z being the components of v): - * <pre> - * ┌ (x^2 * y + z^2) / (x^2 + z^2), x, x * z * (y - 1) / (x^2 + z^2) ┐ - * M = │ x * (y^2 - 1) / (x^2 + z^2), y, z * (y^2 - 1) / (x^2 + z^2) │ - * └ x * z * (y - 1) / (x^2 + z^2), z, (x^2 + z^2 * y) / (x^2 + z^2) ┘ - * </pre> - * - * This is stable as long as v (the bone) is not too much aligned with +/-Y - * (i.e. x and z components are not too close to 0). - * - * Since v is normalized, we have `x^2 + y^2 + z^2 = 1`, - * hence `x^2 + z^2 = 1 - y^2 = (1 - y)(1 + y)`. - * - * This allows to simplifies M like this: - * <pre> - * ┌ 1 - x^2 / (1 + y), x, -x * z / (1 + y) ┐ - * M = │ -x, y, -z │ - * └ -x * z / (1 + y), z, 1 - z^2 / (1 + y) ┘ - * </pre> - * - * Written this way, we see the case v = +Y is no more a singularity. - * The only one - * remaining is the bone being aligned with -Y. - * - * Let's handle - * the asymptotic behavior when bone vector is reaching the limit of y = -1. - * Each of the four corner elements can vary from -1 to 1, - * depending on the axis a chosen for doing the rotation. - * And the "rotation" here is in fact established by mirroring XZ plane by that given axis, - * then inversing the Y-axis. - * For sufficiently small x and z, and with y approaching -1, - * all elements but the four corner ones of M will degenerate. - * So let's now focus on these corner elements. - * - * We rewrite M so that it only contains its four corner elements, - * and combine the `1 / (1 + y)` factor: - * <pre> - * ┌ 1 + y - x^2, -x * z ┐ - * M* = 1 / (1 + y) * │ │ - * └ -x * z, 1 + y - z^2 ┘ - * </pre> - * - * When y is close to -1, computing 1 / (1 + y) will cause severe numerical instability, - * so we ignore it and normalize M instead. - * 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: - * <pre> - * ┌ z^2 - x^2, -2 * x * z ┐ - * M* = 1 / (x^2 + z^2) * │ │ - * └ -2 * x * z, x^2 - z^2 ┘ - * </pre> - */ 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. */ + /** + * Given `v = (v.x, v.y, v.z)` our (normalized) bone vector, we want the rotation matrix M + * from the Y axis (so that `M * (0, 1, 0) = v`). + * - The rotation axis a lays on XZ plane, and it is orthonormal to v, + * hence to the projection of v onto XZ plane. + * - `a = (v.z, 0, -v.x)` + * + * We know a is eigenvector of M (so M * a = a). + * Finally, we have w, such that M * w = (0, 1, 0) + * (i.e. the vector that will be aligned with Y axis once transformed). + * We know w is symmetric to v by the Y axis. + * - `w = (-v.x, v.y, -v.z)` + * + * Solving this, we get (x, y and z being the components of v): + * <pre> + * ┌ (x^2 * y + z^2) / (x^2 + z^2), x, x * z * (y - 1) / (x^2 + z^2) ┐ + * M = │ x * (y^2 - 1) / (x^2 + z^2), y, z * (y^2 - 1) / (x^2 + z^2) │ + * └ x * z * (y - 1) / (x^2 + z^2), z, (x^2 + z^2 * y) / (x^2 + z^2) ┘ + * </pre> + * + * This is stable as long as v (the bone) is not too much aligned with +/-Y + * (i.e. x and z components are not too close to 0). + * + * Since v is normalized, we have `x^2 + y^2 + z^2 = 1`, + * hence `x^2 + z^2 = 1 - y^2 = (1 - y)(1 + y)`. + * + * This allows to simplifies M like this: + * <pre> + * ┌ 1 - x^2 / (1 + y), x, -x * z / (1 + y) ┐ + * M = │ -x, y, -z │ + * └ -x * z / (1 + y), z, 1 - z^2 / (1 + y) ┘ + * </pre> + * + * Written this way, we see the case v = +Y is no more a singularity. + * The only one + * remaining is the bone being aligned with -Y. + * + * Let's handle + * the asymptotic behavior when bone vector is reaching the limit of y = -1. + * Each of the four corner elements can vary from -1 to 1, + * depending on the axis a chosen for doing the rotation. + * And the "rotation" here is in fact established by mirroring XZ plane by that given axis, + * then inversing the Y-axis. + * For sufficiently small x and z, and with y approaching -1, + * all elements but the four corner ones of M will degenerate. + * So let's now focus on these corner elements. + * + * We rewrite M so that it only contains its four corner elements, + * and combine the `1 / (1 + y)` factor: + * <pre> + * ┌ 1 + y - x^2, -x * z ┐ + * M* = 1 / (1 + y) * │ │ + * └ -x * z, 1 + y - z^2 ┘ + * </pre> + * + * When y is close to -1, computing 1 / (1 + y) will cause severe numerical instability, + * 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 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. + */ + + 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; @@ -2269,18 +2205,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. */ @@ -2309,10 +2242,6 @@ void vec_roll_to_mat3(const float vec[3], const float roll, float r_mat[3][3]) /** \name Armature Bone Matrix Calculation (Recursive) * \{ */ -/** - * Recursive part, calculates rest-position of entire tree of children. - * \note Used when exiting edit-mode too. - */ void BKE_armature_where_is_bone(Bone *bone, const Bone *bone_parent, const bool use_recursion) { float vec[3]; @@ -2351,8 +2280,6 @@ void BKE_armature_where_is_bone(Bone *bone, const Bone *bone_parent, const bool } } -/* updates vectors and matrices on rest-position level, only needed - * after editing armature itself, now only on reading file */ void BKE_armature_where_is(bArmature *arm) { Bone *bone; @@ -2569,10 +2496,6 @@ static int rebuild_pose_bone( return counter; } -/** - * Clear pointers of object's pose - * (needed in remap case, since we cannot always wait for a complete pose rebuild). - */ void BKE_pose_clear_pointers(bPose *pose) { LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) { @@ -2594,7 +2517,6 @@ static bPoseChannel *pose_channel_find_bone(bPose *pose, Bone *bone) return (bone != NULL) ? BKE_pose_channel_find_name(pose, bone->name) : NULL; } -/** Update the links for the B-Bone handles from Bone data. */ void BKE_pchan_rebuild_bbone_handles(bPose *pose, bPoseChannel *pchan) { pchan->bbone_prev = pose_channel_find_bone(pose, pchan->bone->bbone_prev); @@ -2612,13 +2534,6 @@ void BKE_pose_channels_clear_with_null_bone(bPose *pose, const bool do_id_user) } } -/** - * Only after leave editmode, duplicating, validating older files, library syncing. - * - * \note pose->flag is set for it. - * - * \param bmain: May be NULL, only used to tag depsgraph as being dirty... - */ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_user) { Bone *bone; @@ -2686,11 +2601,6 @@ void BKE_pose_rebuild(Main *bmain, Object *ob, bArmature *arm, const bool do_id_ } } -/** - * Ensures object's pose is rebuilt if needed. - * - * \param bmain: May be NULL, only used to tag depsgraph as being dirty... - */ void BKE_pose_ensure(Main *bmain, Object *ob, bArmature *arm, const bool do_id_user) { BLI_assert(!ELEM(NULL, arm, ob)); @@ -2706,9 +2616,6 @@ void BKE_pose_ensure(Main *bmain, Object *ob, bArmature *arm, const bool do_id_u /** \name Pose Solver * \{ */ -/** - * Convert the loc/rot/size to \a r_chanmat (typically #bPoseChannel.chan_mat). - */ void BKE_pchan_to_mat4(const bPoseChannel *pchan, float r_chanmat[4][4]) { float smat[3][3]; @@ -2732,8 +2639,6 @@ void BKE_pchan_to_mat4(const bPoseChannel *pchan, float r_chanmat[4][4]) } } -/* loc/rot/size to mat4 */ -/* used in constraint.c too */ void BKE_pchan_calc_mat(bPoseChannel *pchan) { /* this is just a wrapper around the copy of this function which calculates the matrix @@ -2742,7 +2647,6 @@ void BKE_pchan_calc_mat(bPoseChannel *pchan) BKE_pchan_to_mat4(pchan, pchan->chan_mat); } -/* calculate tail of posechannel */ void BKE_pose_where_is_bone_tail(bPoseChannel *pchan) { float vec[3]; @@ -2752,10 +2656,6 @@ void BKE_pose_where_is_bone_tail(bPoseChannel *pchan) add_v3_v3v3(pchan->pose_tail, pchan->pose_head, vec); } -/* The main armature solver, does all constraints excluding IK */ -/* pchan is validated, as having bone and parent pointer - * 'do_extra': when zero skips loc/size/rot, constraints and strip modifiers. - */ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -2763,7 +2663,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); } @@ -2820,8 +2720,6 @@ void BKE_pose_where_is_bone(struct Depsgraph *depsgraph, BKE_pose_where_is_bone_tail(pchan); } -/* This only reads anim data from channels, and writes to channels */ -/* This is the only function adding poses */ void BKE_pose_where_is(struct Depsgraph *depsgraph, Scene *scene, Object *ob) { bArmature *arm; @@ -2966,10 +2864,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 5f721b49361..a8e74f6b4c3 100644 --- a/source/blender/blenkernel/intern/armature_deform.c +++ b/source/blender/blenkernel/intern/armature_deform.c @@ -121,7 +121,6 @@ static void b_bone_deform(const bPoseChannel *pchan, &quats[index + 1], mats[index + 2].mat, co, weight * blend, vec, dq, defmat); } -/* using vec with dist to bone b1 - b2 */ float distfactor_to_bone( const float vec[3], const float b1[3], const float b2[3], float rad1, float rad2, float rdist) { diff --git a/source/blender/blenkernel/intern/armature_test.cc b/source/blender/blenkernel/intern/armature_test.cc index 2994563175f..a6d9a1f41e9 100644 --- a/source/blender/blenkernel/intern/armature_test.cc +++ b/source/blender/blenkernel/intern/armature_test.cc @@ -30,6 +30,36 @@ 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]; @@ -93,73 +123,246 @@ 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); +} - /* If normalized_vector is far enough from -Y, apply the general case. */ - { - 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); +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); \ + } + +/* 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; diff --git a/source/blender/blenkernel/intern/armature_update.c b/source/blender/blenkernel/intern/armature_update.c index 35ae2d2dbef..05c318663e9 100644 --- a/source/blender/blenkernel/intern/armature_update.c +++ b/source/blender/blenkernel/intern/armature_update.c @@ -277,46 +277,59 @@ static void apply_curve_transform( *r_radius = (radius + *r_radius) / 2; } +static float dist_to_sphere_shell(const float sphere_origin[3], + const float sphere_radius, + const float point[3]) +{ + float vec[3]; + sub_v3_v3v3(vec, sphere_origin, point); + return sphere_radius - len_v3(vec); +} + /* This function positions the tail of the bone so that it preserves the length of it. * The length of the bone can be seen as a sphere radius. */ static int position_tail_on_spline(bSplineIKConstraint *ik_data, const float head_pos[3], const float sphere_radius, - const int prev_seg_idx, + int prev_seg_idx, float r_tail_pos[3], float *r_new_curve_pos, float *r_radius) { /* This is using the tessellated curve data. * So we are working with piece-wise linear curve segments. - * The same method is use in #BKE_where_on_path to get curve location data. */ + * The same method is used in #BKE_where_on_path to get curve location data. */ const CurveCache *cache = ik_data->tar->runtime.curve_cache; - const BevList *bl = cache->bev.first; - BevPoint *bp = bl->bevpoints; - const float spline_len = BKE_anim_path_get_length(cache); const float *seg_accum_len = cache->anim_path_accum_length; int max_seg_idx = BKE_anim_path_get_array_size(cache) - 1; - /* Convert our initial intersection point guess to a point index. - * If the curve was a straight line, then pointEnd would be the correct location. + /* Make an initial guess of where our intersection point will be. + * If the curve was a straight line, then the faction passed in r_new_curve_pos + * would be the correct location. * So make it our first initial guess. */ + const float spline_len = BKE_anim_path_get_length(cache); const float guessed_len = *r_new_curve_pos * spline_len; BLI_assert(prev_seg_idx >= 0); - int cur_seg_idx = prev_seg_idx; while (cur_seg_idx < max_seg_idx && guessed_len > seg_accum_len[cur_seg_idx]) { cur_seg_idx++; } + /* Convert the segment to bev points. + * For example, the segment with index 0 will have bev points 0 and 1. + */ int bp_idx = cur_seg_idx + 1; - bp = bp + bp_idx; + const BevList *bl = cache->bev.first; bool is_cyclic = bl->poly >= 0; - BevPoint *prev_bp = bp - 1; + BevPoint *bp = bl->bevpoints; + BevPoint *prev_bp; + bp = bp + bp_idx; + prev_bp = bp - 1; /* Go to the next tessellated curve point until we cross to outside of the sphere. */ while (len_v3v3(head_pos, bp->vec) < sphere_radius) { @@ -337,35 +350,53 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, bp_idx++; } - float isect_1[3], isect_2[3]; - - /* Calculate the intersection point. */ - int ret = isect_line_sphere_v3(prev_bp->vec, bp->vec, head_pos, sphere_radius, isect_1, isect_2); + /* Calculate the intersection point using the secant root finding method */ + float x0 = 0.0f, x1 = 1.0f, x2 = 0.5f; + float x0_point[3], x1_point[3], start_p[3]; + float epsilon = max_fff(1.0f, len_v3(head_pos), len_v3(bp->vec)) * FLT_EPSILON; - if (ret > 0) { - /* Because of how `isect_line_sphere_v3` works, we know that `isect_1` contains the - * intersection point we want. And it will always intersect as we go from inside to outside - * of the sphere. + if (prev_seg_idx == bp_idx - 1) { + /* The intersection lies inside the same segment as the last point. + * Set the last point to be the start search point so we minimize the risks of going backwards + * on the curve. */ - copy_v3_v3(r_tail_pos, isect_1); + copy_v3_v3(start_p, head_pos); } else { - /* Couldn't find an intersection point. This means that the floating point - * values are too small and thus the intersection check fails. - * So assume that the distance is so small that tail_pos == head_pos. - */ - copy_v3_v3(r_tail_pos, head_pos); + copy_v3_v3(start_p, prev_bp->vec); } - cur_seg_idx = bp_idx - 2; + for (int i = 0; i < 10; i++) { + interp_v3_v3v3(x0_point, start_p, bp->vec, x0); + interp_v3_v3v3(x1_point, start_p, bp->vec, x1); + + float f_x0 = dist_to_sphere_shell(head_pos, sphere_radius, x0_point); + float f_x1 = dist_to_sphere_shell(head_pos, sphere_radius, x1_point); + + if (fabsf(f_x1) <= epsilon || f_x0 == f_x1) { + break; + } + + x2 = x1 - f_x1 * (x1 - x0) / (f_x1 - f_x0); + x0 = x1; + x1 = x2; + } + /* Found the bone tail position! */ + copy_v3_v3(r_tail_pos, x1_point); + + /* Because our intersection point lies inside the current segment, + * Convert our bevpoint index back to the previous segment index (-2 instead of -1). + * This is because our actual location is prev_seg_len + isect_seg_len. + */ + prev_seg_idx = bp_idx - 2; float prev_seg_len = 0; - if (cur_seg_idx < 0) { - cur_seg_idx = 0; + if (prev_seg_idx < 0) { + prev_seg_idx = 0; prev_seg_len = 0; } else { - prev_seg_len = seg_accum_len[cur_seg_idx]; + prev_seg_len = seg_accum_len[prev_seg_idx]; } /* Convert the point back into the 0-1 interpolation range. */ @@ -380,7 +411,8 @@ static int position_tail_on_spline(bSplineIKConstraint *ik_data, *r_radius = (1.0f - frac) * prev_bp->radius + frac * bp->radius; } - return cur_seg_idx; + /* Return the current segment. */ + return bp_idx - 1; } /* Evaluate spline IK for a given bone. */ diff --git a/source/blender/blenkernel/intern/asset.cc b/source/blender/blenkernel/intern/asset.cc index ae9ded3c754..e5509b09e20 100644 --- a/source/blender/blenkernel/intern/asset.cc +++ b/source/blender/blenkernel/intern/asset.cc @@ -21,14 +21,12 @@ #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" @@ -41,7 +39,7 @@ using namespace blender; -AssetMetaData *BKE_asset_metadata_create(void) +AssetMetaData *BKE_asset_metadata_create() { AssetMetaData *asset_data = (AssetMetaData *)MEM_callocN(sizeof(*asset_data), __func__); memcpy(asset_data, DNA_struct_default_get(AssetMetaData), sizeof(*asset_data)); @@ -53,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); @@ -79,9 +78,6 @@ AssetTag *BKE_asset_metadata_tag_add(AssetMetaData *asset_data, const char *name return tag; } -/** - * Make sure there is a tag with name \a name, create one if needed. - */ struct AssetTagEnsureResult BKE_asset_metadata_tag_ensure(AssetMetaData *asset_data, const char *name) { @@ -140,6 +136,25 @@ void BKE_asset_metadata_catalog_id_set(struct AssetMetaData *asset_data, 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), @@ -158,6 +173,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); } @@ -169,12 +187,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 index b00f4305aa6..06dd623ff28 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -18,35 +18,29 @@ * \ingroup bke */ +#include <fstream> +#include <set> + #include "BKE_asset_catalog.hh" #include "BKE_asset_library.h" -#include "BKE_preferences.h" -#include "BLI_fileops.h" +#include "BLI_fileops.hh" #include "BLI_path_util.h" -#include "BLI_string_ref.hh" - -#include "DNA_userdef_types.h" /* For S_ISREG() and S_ISDIR() on Windows. */ #ifdef WIN32 # include "BLI_winstuff.h" #endif -#include <fstream> -#include <set> +#include "CLG_log.h" + +static CLG_LogRef LOG = {"bke.asset_service"}; namespace blender::bke { -const char AssetCatalogService::PATH_SEPARATOR = '/'; 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 = @@ -56,39 +50,146 @@ const std::string AssetCatalogDefinitionFile::HEADER = "# 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) - : asset_library_root_(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_; +} + +void AssetCatalogService::tag_all_catalogs_as_unsaved_changes() +{ + for (auto &catalog : catalog_collection_->catalogs_.values()) { + catalog->flags.has_unsaved_changes = true; + } + catalog_collection_->has_unsaved_changes_ = true; } bool AssetCatalogService::is_empty() const { - return catalogs_.is_empty(); + BLI_assert(catalog_collection_); + return catalog_collection_->catalogs_.is_empty(); } -AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) +OwningAssetCatalogMap &AssetCatalogService::get_catalogs() +{ + return catalog_collection_->catalogs_; +} +OwningAssetCatalogMap &AssetCatalogService::get_deleted_catalogs() { - std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id); + 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 CatalogPath &path) const +AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const { - for (const auto &catalog : catalogs_.values()) { + /* 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) { - return catalog.get(); + 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 nullptr; + return AssetCatalogFilter(std::move(matching_catalog_ids), std::move(known_catalog_ids)); } -void AssetCatalogService::delete_catalog(CatalogID catalog_id) +void AssetCatalogService::delete_catalog_by_id_soft(const CatalogID catalog_id) { - std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(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; @@ -98,37 +199,79 @@ void AssetCatalogService::delete_catalog(CatalogID catalog_id) AssetCatalog *catalog = catalog_uptr_ptr->get(); catalog->flags.is_deleted = true; - /* Move ownership from this->catalogs_ to this->deleted_catalogs_. */ - this->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr)); + /* 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. */ - this->catalogs_.remove(catalog_id); + 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::update_catalog_path(CatalogID catalog_id, - const CatalogPath &new_catalog_path) +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 CatalogPath old_cat_path = renamed_cat->path; + const AssetCatalogPath old_cat_path = renamed_cat->path; - for (auto &catalog_uptr : catalogs_.values()) { + for (auto &catalog_uptr : catalog_collection_->catalogs_.values()) { AssetCatalog *cat = catalog_uptr.get(); - if (!cat->is_contained_in(old_cat_path)) { + + 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(); - const CatalogPath path_suffix = cat->path.substr(old_cat_path.length()); - cat->path = new_catalog_path + path_suffix; + /* 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 CatalogPath &catalog_path) +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(); @@ -136,20 +279,18 @@ AssetCatalog *AssetCatalogService::create_catalog(const CatalogPath &catalog_pat /* 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(!catalogs_.contains(catalog->catalog_id), "duplicate catalog ID not supported"); - catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + 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_definition_file_) { + 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_definition_file_->add_new(catalog_ptr); + catalog_collection_->catalog_definition_file_->add_new(catalog_ptr); } - /* The tree may not exist; this happens when no catalog definition file has been loaded yet. When - * the tree is created any in-memory catalogs will be added, so it doesn't need to happen now. */ - if (catalog_tree_) { - catalog_tree_->insert_item(*catalog_ptr); - } + BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist."); + catalog_tree_->insert_item(*catalog_ptr); return catalog_ptr; } @@ -173,7 +314,8 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director { BLI_stat_t status; if (BLI_stat(file_or_directory_path.data(), &status) == -1) { - // TODO(@sybren): throw an appropriate exception. + /* TODO(@sybren): throw an appropriate exception. */ + CLOG_WARN(&LOG, "path not found: %s", file_or_directory_path.data()); return; } @@ -184,22 +326,23 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director load_directory_recursive(file_or_directory_path); } else { - // TODO(@sybren): throw an appropriate exception. + /* TODO(@sybren): throw an appropriate exception. */ } /* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */ - catalog_tree_ = read_into_tree(); + 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. + /* 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. */ + CLOG_INFO(&LOG, 2, "path not found: %s", file_path.data()); return; } @@ -213,9 +356,9 @@ void AssetCatalogService::load_single_file(const CatalogFilePath &catalog_defini std::unique_ptr<AssetCatalogDefinitionFile> cdf = parse_catalog_file( catalog_definition_file_path); - BLI_assert_msg(!this->catalog_definition_file_, + BLI_assert_msg(!catalog_collection_->catalog_definition_file_, "Only loading of a single catalog definition file is supported."); - this->catalog_definition_file_ = std::move(cdf); + catalog_collection_->catalog_definition_file_ = std::move(cdf); } std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_file( @@ -224,18 +367,23 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_f auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); cdf->file_path = catalog_definition_file_path; - auto catalog_parsed_callback = [this, 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 (this->catalogs_.contains(catalog->catalog_id)) { - // TODO(@sybren): apparently another CDF was already loaded. This is not supported yet. + 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. */ - this->catalogs_.add_new(catalog->catalog_id, std::move(catalog)); + catalog_collection_->catalogs_.add_new(catalog->catalog_id, std::move(catalog)); return true; }; @@ -244,57 +392,125 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_f return cdf; } -void AssetCatalogService::merge_from_disk_before_writing() +void AssetCatalogService::reload_catalogs() { /* TODO(Sybren): expand to support multiple CDFs. */ - - if (!catalog_definition_file_ || catalog_definition_file_->file_path.empty() || - !BLI_is_file(catalog_definition_file_->file_path.c_str())) { + AssetCatalogDefinitionFile *const cdf = catalog_collection_->catalog_definition_file_.get(); + if (!cdf || cdf->file_path.empty() || !BLI_is_file(cdf->file_path.c_str())) { return; } - auto catalog_parsed_callback = [this](std::unique_ptr<AssetCatalog> catalog) { - const bUUID catalog_id = catalog->catalog_id; + /* 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; - /* The following two conditions could be or'ed together. Keeping them separated helps when - * adding debug prints, breakpoints, etc. */ - if (this->catalogs_.contains(catalog_id)) { - /* This catalog was already seen, so just ignore it. */ - return false; - } - if (this->deleted_catalogs_.contains(catalog_id)) { - /* This catalog was already seen and subsequently deleted, so just ignore it. */ + 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 a new catalog, so let's keep it around. */ - this->catalogs_.add_new(catalog_id, std::move(catalog)); + /* 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; }; - catalog_definition_file_->parse_catalog_file(catalog_definition_file_->file_path, - catalog_parsed_callback); + 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_on_blendfile_save(const CatalogFilePath &blend_file_path) +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 (this->catalog_definition_file_) { - merge_from_disk_before_writing(); - return catalog_definition_file_->write_to_disk(); + if (catalog_collection_->catalog_definition_file_) { + reload_catalogs(); + return catalog_collection_->catalog_definition_file_->write_to_disk(); } - if (catalogs_.is_empty() && deleted_catalogs_.is_empty()) { + 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); - this->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write); - merge_from_disk_before_writing(); - return catalog_definition_file_->write_to_disk(); + catalog_collection_->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write); + reload_catalogs(); + return catalog_collection_->catalog_definition_file_->write_to_disk(); +} + +void AssetCatalogService::prepare_to_merge_on_write() +{ + /* TODO(Sybren): expand to support multiple CDFs. */ + + if (!catalog_collection_->catalog_definition_file_) { + /* There is no CDF connected, so it's a no-op. */ + return; + } + + /* Remove any association with the CDF, so that a new location will be chosen + * when the blend file is saved. */ + catalog_collection_->catalog_definition_file_.reset(); + + /* Mark all in-memory catalogs as "dirty", to force them to be kept around on + * the next "load-merge-write" cycle. */ + tag_all_catalogs_as_unsaved_changes(); } CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( @@ -304,31 +520,26 @@ CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing( "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); - - if (BLI_exists(cdf_path_next_to_blend.c_str())) { - /* - The directory containing the blend file has a blender_assets.cats.txt file? - * -> Merge with & write to that file. */ - return cdf_path_next_to_blend; - } - - /* - There's no definition file next to the .blend file. - * -> Ask the asset library API for an appropriate location. */ - char suitable_root_path[PATH_MAX]; - BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), - suitable_root_path); - 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; + return cdf_path_next_to_blend; } std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_in_memory( @@ -337,19 +548,24 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_i auto cdf = std::make_unique<AssetCatalogDefinitionFile>(); cdf->file_path = file_path; - for (auto &catalog : catalogs_.values()) { + 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 : catalogs_.values()) { + for (auto &catalog : catalog_collection_->catalogs_.values()) { tree->insert_item(*catalog); } @@ -358,15 +574,122 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_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), parent_(parent) + : name_(name), catalog_id_(catalog_id), simple_name_(simple_name), parent_(parent) { } @@ -375,16 +698,25 @@ CatalogID AssetCatalogTreeItem::get_catalog_id() const return catalog_id_; } -StringRef AssetCatalogTreeItem::get_name() const +StringRefNull AssetCatalogTreeItem::get_name() const { return name_; } -CatalogPath AssetCatalogTreeItem::catalog_path() const +StringRefNull AssetCatalogTreeItem::get_simple_name() const +{ + return simple_name_; +} +bool AssetCatalogTreeItem::has_unsaved_changes() const +{ + return has_unsaved_changes_; +} + +AssetCatalogPath AssetCatalogTreeItem::catalog_path() const { - std::string current_path = name_; + AssetCatalogPath current_path = name_; for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) { - current_path = parent->name_ + AssetCatalogService::PATH_SEPARATOR + current_path; + current_path = AssetCatalogPath(parent->name_) / current_path; } return current_path; } @@ -403,34 +735,24 @@ bool AssetCatalogTreeItem::has_children() const return !children_.empty(); } -/* ---------------------------------------------------------------------- */ - -/** - * Iterate over path components, calling \a callback for each component. E.g. "just/some/path" - * iterates over "just", then "some" then "path". - */ -static void iterate_over_catalog_path_components( - const CatalogPath &path, - FunctionRef<void(StringRef component_name, bool is_last_component)> callback) +void AssetCatalogTreeItem::foreach_item_recursive(AssetCatalogTreeItem::ChildMap &children, + const ItemIterFn callback) { - const char *next_slash_ptr; - - for (const char *path_component = 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) { - 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); + for (auto &[key, item] : children) { + callback(item); + foreach_item_recursive(item.children_, callback); + } +} - callback(component_name, is_last_component); +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; @@ -438,30 +760,34 @@ void AssetCatalogTree::insert_item(const AssetCatalog &catalog) * added to (if not there yet). */ AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_; - BLI_assert_msg(!ELEM(catalog.path[0], '/', '\\'), + BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'), "Malformed catalog path; should not start with a separator"); const CatalogID nil_id{}; - iterate_over_catalog_path_components( - catalog.path, [&](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, 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 && BLI_uuid_is_nil(item.catalog_id_)) { - item.catalog_id_ = catalog.catalog_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_; - }); + /* 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) @@ -469,15 +795,6 @@ void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback) AssetCatalogTreeItem::foreach_item_recursive(root_items_, callback); } -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 AssetCatalogTree::foreach_root_item(const ItemIterFn callback) { for (auto &[key, item] : root_items_) { @@ -485,17 +802,9 @@ void AssetCatalogTree::foreach_root_item(const ItemIterFn callback) } } -void AssetCatalogTreeItem::foreach_child(const ItemIterFn callback) -{ - for (auto &[key, item] : children_) { - callback(item); - } -} +/* ---------------------------------------------------------------------- */ -AssetCatalogTree *AssetCatalogService::get_catalog_tree() -{ - return catalog_tree_.get(); -} +/* ---------------------------------------------------------------------- */ bool AssetCatalogDefinitionFile::contains(const CatalogID catalog_id) const { @@ -507,12 +816,26 @@ 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); + fstream infile(catalog_definition_file_path, std::ios::in); + if (!infile.is_open()) { + CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str()); + return; + } bool seen_version_number = false; std::string line; while (std::getline(infile, line)) { @@ -544,16 +867,8 @@ void AssetCatalogDefinitionFile::parse_catalog_file( continue; } - if (this->contains(non_owning_ptr->catalog_id)) { - std::cerr << catalog_definition_file_path << ": multiple definitions of catalog " - << non_owning_ptr->catalog_id << " in the same file, using first occurrence." - << std::endl; - /* Don't store 'catalog'; unique_ptr will free its memory. */ - continue; - } - /* The AssetDefinitionFile should include this catalog when writing it back to disk. */ - this->add_new(non_owning_ptr); + this->add_overwrite(non_owning_ptr); } } @@ -592,7 +907,7 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con const StringRef path_and_simple_name = line.substr(first_delim + 1); const int64_t second_delim = path_and_simple_name.find_first_of(delim); - CatalogPath catalog_path; + 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. */ @@ -601,16 +916,16 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con if (second_delim == StringRef::not_found) { /* No delimiter means no simple name, just treat it as all "path". */ - catalog_path = path_and_simple_name; + path_in_file = path_and_simple_name; simple_name = ""; } else { - catalog_path = path_and_simple_name.substr(0, second_delim); + path_in_file = path_and_simple_name.substr(0, second_delim); simple_name = path_and_simple_name.substr(second_delim + 1).trim(); } - catalog_path = AssetCatalog::cleanup_path(catalog_path); - return std::make_unique<AssetCatalog>(catalog_id, catalog_path, simple_name); + AssetCatalogPath catalog_path = path_in_file; + return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name); } bool AssetCatalogDefinitionFile::write_to_disk() const @@ -651,18 +966,18 @@ bool AssetCatalogDefinitionFile::write_to_disk_unsafe(const CatalogFilePath &des return false; } - std::ofstream output(dest_file_path); + fstream output(dest_file_path, std::ios::out); - // TODO(@sybren): remember the line ending style that was originally read, then use that to write - // the file again. + /* TODO(@sybren): remember the line ending style that was originally read, then use that to write + * the file again. */ - // Write the header. + /* 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). + /* 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) { @@ -715,26 +1030,59 @@ bool AssetCatalogDefinitionFile::ensure_directory_exists( 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 CatalogPath &path, + 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 CatalogPath &path) +std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const AssetCatalogPath &path) { - const CatalogPath clean_path = cleanup_path(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; } -std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path) +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; - std::replace(name.begin(), name.end(), AssetCatalogService::PATH_SEPARATOR, '-'); + std::string name = path.str(); + std::replace(name.begin(), name.end(), AssetCatalogPath::SEPARATOR, '-'); if (name.length() < MAX_NAME - 1) { return name; } @@ -744,33 +1092,24 @@ std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path) return "..." + name.substr(name.length() - 60); } -CatalogPath AssetCatalog::cleanup_path(const CatalogPath &path) +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)) { - /* TODO(@sybren): maybe go over each element of the path, and trim those? */ - CatalogPath clean_path = StringRef(path).trim().trim(AssetCatalogService::PATH_SEPARATOR).trim(); - return clean_path; } -bool AssetCatalog::is_contained_in(const CatalogPath &other_path) const +bool AssetCatalogFilter::contains(const CatalogID asset_catalog_id) const { - if (other_path.empty()) { - return true; - } - - if (this->path == other_path) { - return true; - } + return matching_catalog_ids.contains(asset_catalog_id); +} - /* To be a child path of 'other_path', our path must be at least a separator and another - * character longer. */ - if (this->path.length() < other_path.length() + 2) { +bool AssetCatalogFilter::is_known(const CatalogID asset_catalog_id) const +{ + if (BLI_uuid_is_nil(asset_catalog_id)) { return false; } - - const StringRef this_path(this->path); - const bool prefix_ok = this_path.startswith(other_path); - const char next_char = this_path[other_path.length()]; - return prefix_ok && next_char == AssetCatalogService::PATH_SEPARATOR; + 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..d789150dba5 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_catalog_path.cc @@ -0,0 +1,238 @@ +/* + * 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); +} + +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 index 5b94f021797..8c39bfc9770 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -24,8 +24,11 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" +#include "DNA_asset_types.h" #include "DNA_userdef_types.h" +#include "CLG_log.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -35,10 +38,12 @@ 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"); @@ -55,7 +60,33 @@ class TestableAssetCatalogService : public AssetCatalogService { AssetCatalogDefinitionFile *get_catalog_definition_file() { - return catalog_definition_file_.get(); + 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; } }; @@ -64,6 +95,18 @@ class AssetCatalogTest : public testing::Test { CatalogFilePath asset_library_root_; CatalogFilePath temp_library_path_; + static void SetUpTestSuite() + { + testing::Test::SetUpTestSuite(); + CLG_init(); + } + + static void TearDownTestSuite() + { + CLG_exit(); + testing::Test::TearDownTestSuite(); + } + void SetUp() override { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); @@ -75,6 +118,14 @@ class AssetCatalogTest : public testing::Test { 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() @@ -92,21 +143,23 @@ class AssetCatalogTest : public testing::Test { return path; } - struct CatalogPathInfo { - StringRef name; - int parent_count; - }; - - void assert_expected_item(const CatalogPathInfo &expected_path, + void assert_expected_item(const AssetCatalogPath &expected_path, const AssetCatalogTreeItem &actual_item) { - char expected_filename[FILE_MAXFILE]; + 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", ... */ - BLI_split_file_part(expected_path.name.data(), expected_filename, sizeof(expected_filename)); - EXPECT_EQ(expected_filename, actual_item.get_name()); + EXPECT_EQ(expected_path.name(), actual_item.get_name()); + /* Does the computed number of parents match? */ - EXPECT_EQ(expected_path.parent_count, actual_item.count_parents()); - EXPECT_EQ(expected_path.name, actual_item.catalog_path()); + 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()); } /** @@ -114,7 +167,7 @@ class AssetCatalogTest : public testing::Test { * the items map exactly to \a expected_paths. */ void assert_expected_tree_items(AssetCatalogTree *tree, - const std::vector<CatalogPathInfo> &expected_paths) + const std::vector<AssetCatalogPath> &expected_paths) { int i = 0; tree->foreach_item([&](const AssetCatalogTreeItem &actual_item) { @@ -131,7 +184,7 @@ class AssetCatalogTest : public testing::Test { * #AssetCatalogTree::foreach_root_item() instead of #AssetCatalogTree::foreach_item(). */ void assert_expected_tree_root_items(AssetCatalogTree *tree, - const std::vector<CatalogPathInfo> &expected_paths) + const std::vector<AssetCatalogPath> &expected_paths) { int i = 0; tree->foreach_root_item([&](const AssetCatalogTreeItem &actual_item) { @@ -149,7 +202,7 @@ class AssetCatalogTest : public testing::Test { * #AssetCatalogTreeItem::foreach_child() instead of #AssetCatalogTree::foreach_item(). */ void assert_expected_tree_item_child_items(AssetCatalogTreeItem *parent_item, - const std::vector<CatalogPathInfo> &expected_paths) + const std::vector<AssetCatalogPath> &expected_paths) { int i = 0; parent_item->foreach_child([&](const AssetCatalogTreeItem &actual_item) { @@ -161,12 +214,74 @@ class AssetCatalogTest : public testing::Test { }); } - void TearDown() override + /* 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) { - if (!temp_library_path_.empty()) { - BLI_delete(temp_library_path_.c_str(), true, true); - temp_library_path_ = ""; + 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); } }; @@ -186,22 +301,72 @@ TEST_F(AssetCatalogTest, load_single_file) 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); + 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); + 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); + 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) @@ -219,32 +384,30 @@ TEST_F(AssetCatalogTest, insert_item_into_tree) std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item"); tree.insert_item(*catalog); - assert_expected_tree_items(&tree, {{"item", 0}}); + 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", 0}, {"item/child", 1}}); + assert_expected_tree_items(&tree, {"item", "item/child"}); - std::vector<CatalogPathInfo> expected_paths; + 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", 0}, - {"item/child", 1}, - {"item/child/grandchild", 2}, - {"item/child/grandchild/grandgrandchild", 3}}; + 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", 0}, - {"item/child", 1}, - {"item/child/grandchild", 2}, - {"item/child/grandchild/grandgrandchild", 3}, - {"root level", 0}}; + expected_paths = {"item", + "item/child", + "item/child/grandchild", + "item/child/grandchild/grandgrandchild", + "root level"}; assert_expected_tree_items(&tree, expected_paths); } @@ -253,7 +416,7 @@ TEST_F(AssetCatalogTest, insert_item_into_tree) std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("item/child"); tree.insert_item(*catalog); - assert_expected_tree_items(&tree, {{"item", 0}, {"item/child", 1}}); + assert_expected_tree_items(&tree, {"item", "item/child"}); } { @@ -261,7 +424,7 @@ TEST_F(AssetCatalogTest, insert_item_into_tree) std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("white space"); tree.insert_item(*catalog); - assert_expected_tree_items(&tree, {{"white space", 0}}); + assert_expected_tree_items(&tree, {"white space"}); } { @@ -269,7 +432,7 @@ TEST_F(AssetCatalogTest, insert_item_into_tree) std::unique_ptr<AssetCatalog> catalog = AssetCatalog::from_path("/item/white space"); tree.insert_item(*catalog); - assert_expected_tree_items(&tree, {{"item", 0}, {"item/white space", 1}}); + assert_expected_tree_items(&tree, {"item", "item/white space"}); } { @@ -277,11 +440,11 @@ TEST_F(AssetCatalogTest, insert_item_into_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", 0}}); + 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", 0}, {"Ružena/Ružena", 1}}); + assert_expected_tree_items(&tree, {"Ružena", "Ružena/Ružena"}); } } @@ -292,19 +455,20 @@ TEST_F(AssetCatalogTest, load_single_file_into_tree) /* Contains not only paths from the CDF but also the missing parents (implicitly defined * catalogs). */ - std::vector<CatalogPathInfo> expected_paths{ - {"character", 0}, - {"character/Ellie", 1}, - {"character/Ellie/poselib", 2}, - {"character/Ellie/poselib/tailslash", 3}, - {"character/Ellie/poselib/white space", 3}, - {"character/Ružena", 1}, - {"character/Ružena/poselib", 2}, - {"character/Ružena/poselib/face", 3}, - {"character/Ružena/poselib/hand", 3}, - {"path", 0}, /* Implicit. */ - {"path/without", 1}, /* Implicit. */ - {"path/without/simplename", 2}, /* From CDF. */ + 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(); @@ -315,7 +479,7 @@ TEST_F(AssetCatalogTest, foreach_in_tree) { { AssetCatalogTree tree{}; - const std::vector<CatalogPathInfo> no_catalogs{}; + const std::vector<AssetCatalogPath> no_catalogs{}; assert_expected_tree_items(&tree, no_catalogs); assert_expected_tree_root_items(&tree, no_catalogs); @@ -330,16 +494,16 @@ TEST_F(AssetCatalogTest, foreach_in_tree) AssetCatalogService service(asset_library_root_); service.load_from_disk(asset_library_root_ + "/" + "blender_assets.cats.txt"); - std::vector<CatalogPathInfo> expected_root_items{{"character", 0}, {"path", 0}}; + 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<CatalogPathInfo>> expected_root_child_items = { + std::vector<std::vector<AssetCatalogPath>> expected_root_child_items = { /* Children of the "character" root item. */ - {{"character/Ellie", 1}, {"character/Ružena", 1}}, + {"character/Ellie", "character/Ružena"}, /* Children of the "path" root item. */ - {{"path/without", 1}}, + {"path/without"}, }; int i = 0; tree->foreach_root_item([&expected_root_child_items, &i, this](AssetCatalogTreeItem &item) { @@ -399,11 +563,35 @@ TEST_F(AssetCatalogTest, write_single_file) /* TODO(@sybren): test ordering of catalogs in the file. */ } +TEST_F(AssetCatalogTest, read_write_unicode_filepath) +{ + TestableAssetCatalogService service(asset_library_root_); + const CatalogFilePath load_from_path = asset_library_root_ + "/новый/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + service.load_from_disk(load_from_path); + + const CatalogFilePath save_to_path = use_temp_path() + "новый.cats.txt"; + AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); + ASSERT_NE(nullptr, cdf) << "unable to load " << load_from_path; + EXPECT_TRUE(cdf->write_to_disk(save_to_path)); + + AssetCatalogService loaded_service(save_to_path); + loaded_service.load_from_disk(); + + /* Test that the file was loaded correctly. */ + const bUUID materials_uuid("a2151dff-dead-4f29-b6bc-b2c7d6cccdb4"); + const AssetCatalog *cat = loaded_service.find_catalog(materials_uuid); + ASSERT_NE(nullptr, cat); + EXPECT_EQ(materials_uuid, cat->catalog_id); + EXPECT_EQ(AssetCatalogPath("Материалы"), cat->path); + EXPECT_EQ("Russian Materials", cat->simple_name); +} + TEST_F(AssetCatalogTest, no_writing_empty_files) { const CatalogFilePath temp_lib_root = create_temp_path(); AssetCatalogService service(temp_lib_root); - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + service.write_to_disk(temp_lib_root + "phony.blend"); const CatalogFilePath default_cdf_path = temp_lib_root + AssetCatalogService::DEFAULT_CATALOG_FILENAME; @@ -429,7 +617,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf) 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_on_blendfile_save(blendfilename.c_str())); + 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. */ @@ -456,7 +644,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory) const AssetCatalog *cat = service.create_catalog("some/catalog/path"); const CatalogFilePath blendfilename = target_dir + "some_file.blend"; - ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename.c_str())); + ASSERT_TRUE(service.write_to_disk(blendfilename)); /* Test that the CDF was created in the expected location. */ const CatalogFilePath expected_cdf_path = target_dir + @@ -479,8 +667,8 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me { const CatalogFilePath target_dir = create_temp_path(); /* Has trailing slash. */ const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; - const CatalogFilePath writable_cdf_file = target_dir + - AssetCatalogService::DEFAULT_CATALOG_FILENAME; + 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. */ @@ -489,7 +677,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me /* 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_on_blendfile_save(blendfilename.c_str())); + ASSERT_TRUE(service.write_to_disk(blendfilename)); /* Test that the CDF still exists in the expected location. */ const CatalogFilePath backup_filename = writable_cdf_file + "~"; @@ -509,51 +697,21 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_POSES_ELLIE)); } -/* Create some catalogs in memory, save to subdirectory of a registered asset library. */ -TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_asset_lib) +/* 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) { - 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/"; - CatalogFilePath writable_cdf_file = registered_asset_lib + - AssetCatalogService::DEFAULT_CATALOG_FILENAME; - BLI_path_slash_native(writable_cdf_file.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(registered_asset_lib.c_str())); - 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 CatalogFilePath blenddirname = registered_asset_lib + "subdirectory/"; - const CatalogFilePath blendfilename = blenddirname + "some_file.blend"; - ASSERT_TRUE(BLI_dir_create_recursive(blenddirname.c_str())); - 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_on_blendfile_save(blendfilename.c_str())); - - /* Test that the CDF still exists in the expected location. */ - EXPECT_TRUE(BLI_exists(writable_cdf_file.c_str())); - const CatalogFilePath backup_filename = writable_cdf_file + "~"; - 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(); - BLI_path_slash_native(cdf->file_path.data()); - 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)); + save_from_memory_into_existing_asset_lib(true); +} - BKE_preferences_asset_library_remove(&U, asset_lib_pref); +/* 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) @@ -574,7 +732,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); /* Writing to disk should create the directory + the default file. */ - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + 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 + "/" + @@ -588,7 +746,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) 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); + EXPECT_EQ(written_cat->path, cat->path.str()); } TEST_F(AssetCatalogTest, create_catalog_after_loading_file) @@ -625,7 +783,7 @@ TEST_F(AssetCatalogTest, create_catalog_after_loading_file) << "expecting newly added catalog to not yet be saved to " << temp_lib_root; /* Write and reload the catalog file. */ - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + 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)) @@ -640,7 +798,7 @@ TEST_F(AssetCatalogTest, create_catalog_path_cleanup) AssetCatalog *cat = service.create_catalog(" /some/path / "); EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); - EXPECT_EQ("some/path", cat->path); + EXPECT_EQ("some/path", cat->path.str()); EXPECT_EQ("some-path", cat->simple_name); } @@ -652,7 +810,7 @@ TEST_F(AssetCatalogTest, create_catalog_simple_name) EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id)); EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands", - cat->path); + cat->path.str()); EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name); } @@ -663,24 +821,79 @@ TEST_F(AssetCatalogTest, delete_catalog_leaf) /* Delete a leaf catalog, i.e. one that is not a parent of another catalog. * This keeps this particular test easy. */ - service.delete_catalog(UUID_POSES_RUZENA_HAND); + 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<CatalogPathInfo> expected_paths{ - {"character", 0}, - {"character/Ellie", 1}, - {"character/Ellie/poselib", 2}, - {"character/Ellie/poselib/tailslash", 3}, - {"character/Ellie/poselib/white space", 3}, - {"character/Ružena", 1}, - {"character/Ružena/poselib", 2}, - {"character/Ružena/poselib/face", 3}, - // {"character/Ružena/poselib/hand", 3}, /* This is the deleted one. */ - {"path", 0}, - {"path/without", 1}, - {"path/without/simplename", 2}, + 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(); @@ -693,7 +906,7 @@ TEST_F(AssetCatalogTest, delete_catalog_write_to_disk) service.load_from_disk(asset_library_root_ + "/" + AssetCatalogService::DEFAULT_CATALOG_FILENAME); - service.delete_catalog(UUID_POSES_ELLIE); + service.delete_catalog_by_id_soft(UUID_POSES_ELLIE); const CatalogFilePath save_to_path = use_temp_path(); AssetCatalogDefinitionFile *cdf = service.get_catalog_definition_file(); @@ -718,7 +931,7 @@ TEST_F(AssetCatalogTest, update_catalog_path) AssetCatalogService::DEFAULT_CATALOG_FILENAME); const AssetCatalog *orig_cat = service.find_catalog(UUID_POSES_RUZENA); - const CatalogPath orig_path = orig_cat->path; + const AssetCatalogPath orig_path = orig_cat->path; service.update_catalog_path(UUID_POSES_RUZENA, "charlib/Ružena"); @@ -728,17 +941,94 @@ TEST_F(AssetCatalogTest, update_catalog_path) 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->simple_name, renamed_cat->simple_name) - << "Changing the path should not change the simple name."; 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) + 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) + 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) + 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_longer_than_simplename) +{ + AssetCatalogService service(asset_library_root_); + service.load_from_disk(asset_library_root_ + "/" + + AssetCatalogService::DEFAULT_CATALOG_FILENAME); + const std::string new_path = + "this/is/a/very/long/path/that/exceeds/the/simple-name/length/of/assets"; + ASSERT_GT(new_path.length(), sizeof(AssetMetaData::catalog_simple_name)) + << "This test case should work with paths longer than AssetMetaData::catalog_simple_name"; + + service.update_catalog_path(UUID_POSES_RUZENA, new_path); + + const std::string new_simple_name = service.find_catalog(UUID_POSES_RUZENA)->simple_name; + EXPECT_LT(new_simple_name.length(), sizeof(AssetMetaData::catalog_simple_name)) + << "The new simple name should fit in the asset metadata."; + EXPECT_EQ("...very-long-path-that-exceeds-the-simple-name-length-of-assets", new_simple_name) + << "Changing the path should update the simplename."; + EXPECT_EQ("...long-path-that-exceeds-the-simple-name-length-of-assets-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."; } @@ -758,24 +1048,100 @@ TEST_F(AssetCatalogTest, merge_catalog_files) * 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. */ - service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + /* 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_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)); EXPECT_NE(nullptr, loaded_service.find_catalog(UUID_AGENT_47)); /* New in the modified file. */ - /* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */ + /* 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/face", ruzena_face->path); + 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) @@ -786,10 +1152,10 @@ TEST_F(AssetCatalogTest, backups) ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); /* Read a CDF, modify, and write it. */ - AssetCatalogService service(cdf_dir); + TestableAssetCatalogService service(cdf_dir); service.load_from_disk(); - service.delete_catalog(UUID_POSES_ELLIE); - service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + 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())); @@ -846,21 +1212,338 @@ TEST_F(AssetCatalogTest, order_by_path) } } -TEST_F(AssetCatalogTest, is_contained_in) +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) { - const AssetCatalog cat(BLI_uuid_generate_random(), "simple/path/child", ""); + 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(cat.is_contained_in("unrelated")); - EXPECT_FALSE(cat.is_contained_in("sim")); - EXPECT_FALSE(cat.is_contained_in("simple/pathx")); - EXPECT_FALSE(cat.is_contained_in("simple/path/c")); - EXPECT_FALSE(cat.is_contained_in("simple/path/child/grandchild")); - EXPECT_FALSE(cat.is_contained_in("simple/path/")) - << "Non-normalized paths are not expected to work."; + 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_TRUE(cat.is_contained_in("")); - EXPECT_TRUE(cat.is_contained_in("simple")); - EXPECT_TRUE(cat.is_contained_in("simple/path")); + 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 index 1086efe45fd..74de9b93c25 100644 --- a/source/blender/blenkernel/intern/asset_library.cc +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -18,18 +18,20 @@ * \ingroup bke */ +#include <memory> + #include "BKE_asset_library.hh" -#include "BKE_callbacks.h" #include "BKE_main.h" #include "BKE_preferences.h" #include "BLI_path_util.h" +#include "DNA_asset_types.h" #include "DNA_userdef_types.h" -#include "MEM_guardedalloc.h" +#include "asset_library_service.hh" -#include <memory> +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 @@ -37,17 +39,21 @@ */ struct AssetLibrary *BKE_asset_library_load(const char *library_path) { - blender::bke::AssetLibrary *lib = new blender::bke::AssetLibrary(); - lib->on_save_handler_register(); - lib->load(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); } -void BKE_asset_library_free(struct AssetLibrary *asset_library) +bool BKE_asset_library_has_any_unsaved_catalogs() { - blender::bke::AssetLibrary *lib = reinterpret_cast<blender::bke::AssetLibrary *>(asset_library); - lib->on_save_handler_unregister(); - delete lib; + 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, @@ -65,11 +71,52 @@ bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path, 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); + return BKE_asset_library_find_suitable_root_path_from_path(bmain->filepath, 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); @@ -77,6 +124,11 @@ void AssetLibrary::load(StringRefNull library_root_directory) 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, @@ -84,11 +136,12 @@ void asset_library_on_save_post(struct Main *main, void *arg) { AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg); - asset_lib->on_save_post(main, pointers, num_pointers); + asset_lib->on_blend_save_post(main, pointers, num_pointers); } + } // namespace -void AssetLibrary::on_save_handler_register() +void AssetLibrary::on_blend_save_handler_register() { /* The callback system doesn't own `on_save_callback_store_`. */ on_save_callback_store_.alloc = false; @@ -99,20 +152,38 @@ void AssetLibrary::on_save_handler_register() BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); } -void AssetLibrary::on_save_handler_unregister() +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_save_post(struct Main *main, - struct PointerRNA ** /*pointers*/, - const int /*num_pointers*/) +void AssetLibrary::on_blend_save_post(struct Main *main, + struct PointerRNA ** /*pointers*/, + const int /*num_pointers*/) { if (this->catalog_service == nullptr) { return; } - this->catalog_service->write_to_disk_on_blendfile_save(main->name); + if (save_catalogs_when_file_is_saved) { + this->catalog_service->write_to_disk(main->filepath); + } } +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..6b3f1fa3408 --- /dev/null +++ b/source/blender/blenkernel/intern/asset_library_service.cc @@ -0,0 +1,161 @@ +/* + * 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(); +} + +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..9fa6d100a53 --- /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 index 37686175aed..702008fed96 100644 --- a/source/blender/blenkernel/intern/asset_library_test.cc +++ b/source/blender/blenkernel/intern/asset_library_test.cc @@ -20,12 +20,36 @@ #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 { -TEST(AssetLibraryTest, load_and_free_c_functions) +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()) { @@ -49,12 +73,10 @@ TEST(AssetLibraryTest, load_and_free_c_functions) 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); - - BKE_asset_library_free(library_c_ptr); + EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); } -TEST(AssetLibraryTest, load_nonexistent_directory) +TEST_F(AssetLibraryTest, load_nonexistent_directory) { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); if (test_files_dir.empty()) { @@ -75,8 +97,6 @@ TEST(AssetLibraryTest, load_nonexistent_directory) /* Check that the catalog service doesn't have any catalogs. */ EXPECT_TRUE(service->is_empty()); - - BKE_asset_library_free(library_c_ptr); } } // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index c2837b522c4..cc43a3e26a8 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -23,21 +23,20 @@ #include "BKE_geometry_set.hh" #include "BKE_mesh.h" #include "BKE_pointcloud.h" +#include "BKE_type_conversions.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_pointcloud_types.h" #include "BLI_color.hh" -#include "BLI_float2.hh" +#include "BLI_math_vec_types.hh" #include "BLI_span.hh" #include "BLT_translation.h" #include "CLG_log.h" -#include "NOD_type_conversions.hh" - #include "attribute_access_intern.hh" static CLG_LogRef LOG = {"bke.attribute_access"}; @@ -50,8 +49,7 @@ 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::GVArrayImpl_For_GSpan; namespace blender::bke { @@ -165,16 +163,18 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_ static int attribute_domain_priority(const AttributeDomain domain) { switch (domain) { - case ATTR_DOMAIN_CURVE: + case ATTR_DOMAIN_INSTANCE: return 0; - case ATTR_DOMAIN_FACE: + case ATTR_DOMAIN_CURVE: return 1; - case ATTR_DOMAIN_EDGE: + case ATTR_DOMAIN_FACE: return 2; - case ATTR_DOMAIN_POINT: + case ATTR_DOMAIN_EDGE: return 3; - case ATTR_DOMAIN_CORNER: + case ATTR_DOMAIN_POINT: return 4; + case ATTR_DOMAIN_CORNER: + return 5; default: /* Domain not supported in nodes yet. */ BLI_assert_unreachable(); @@ -182,10 +182,6 @@ static int attribute_domain_priority(const AttributeDomain domain) } } -/** - * Domains with a higher "information density" have a higher priority, in order - * to choose a domain that will not lose data through domain conversion. - */ AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains) { int highest_priority = INT_MIN; @@ -202,6 +198,17 @@ 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; @@ -222,23 +229,140 @@ OutputAttribute::~OutputAttribute() } } -GVArrayPtr BuiltinCustomDataLayerProvider::try_get_for_read( - const GeometryComponent &component) const +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(varray.index_range(), 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, alloctype, layer_data, 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(varray.index_range(), 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(); +} + +GVArray BuiltinCustomDataLayerProvider::try_get_for_read(const GeometryComponent &component) const { const CustomData *custom_data = custom_data_access_.get_const_custom_data(component); if (custom_data == nullptr) { 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) { @@ -249,19 +373,42 @@ 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); + if (custom_data_access_.update_custom_data_pointers) { + 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 @@ -275,48 +422,27 @@ 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) { - custom_data_access_.update_custom_data_pointers(component); - } - 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; + if (custom_data_access_.update_custom_data_pointers) { + custom_data_access_.update_custom_data_pointers(component); } } - - BLI_assert_unreachable(); - return false; + return delete_success; } bool BuiltinCustomDataLayerProvider::try_create(GeometryComponent &component, @@ -329,16 +455,29 @@ 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); + if (custom_data_access_.update_custom_data_pointers) { + custom_data_access_.update_custom_data_pointers(component); + } } return success; } @@ -349,20 +488,10 @@ 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; -} - -static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer, - const AttributeIDRef &attribute_id) -{ - if (!attribute_id) { - return false; + if (stored_as_named_attribute_) { + return CustomData_get_layer_named(custom_data, stored_type_, name_.c_str()) != nullptr; } - if (attribute_id.is_anonymous()) { - return layer.anonymous_id == &attribute_id.anonymous_id(); - } - return layer.name == attribute_id.name(); + return CustomData_get_layer(custom_data, stored_type_) != nullptr; } ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( @@ -377,23 +506,12 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read( 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 {GVArray::ForSpan(data), domain_}; } return {}; } @@ -418,23 +536,12 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write( CustomData_duplicate_referenced_layer_anonymous( custom_data, layer.type, &attribute_id.anonymous_id(), domain_size); } - const CustomDataType data_type = (CustomDataType)layer.type; - switch (data_type) { - case CD_PROP_FLOAT: - 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; + const CPPType *type = custom_data_type_to_cpp_type((CustomDataType)layer.type); + if (type == nullptr) { + continue; } + GMutableSpan data{*type, layer.data, domain_size}; + return {GVMutableArray::ForSpan(data), domain_}; } return {}; } @@ -458,62 +565,6 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component, 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; -} - bool CustomDataAttributeProvider::try_create(GeometryComponent &component, const AttributeIDRef &attribute_id, const AttributeDomain domain, @@ -552,13 +603,7 @@ 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}; - AttributeIDRef attribute_id; - if (layer.anonymous_id != nullptr) { - attribute_id = layer.anonymous_id; - } - else { - attribute_id = layer.name; - } + const AttributeIDRef attribute_id = attribute_id_from_custom_data_layer(layer); if (!callback(attribute_id, meta_data)) { return false; } @@ -600,7 +645,9 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write( void *data_new = CustomData_duplicate_referenced_layer_named( custom_data, stored_type_, layer.name, domain_size); if (data_old != data_new) { - custom_data_access_.update_custom_data_pointers(component); + if (custom_data_access_.update_custom_data_pointers) { + custom_data_access_.update_custom_data_pointers(component); + } } return {as_write_attribute_(layer.data, domain_size), domain_}; } @@ -622,7 +669,9 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component, 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); + if (custom_data_access_.update_custom_data_pointers) { + custom_data_access_.update_custom_data_pointers(component); + } return true; } } @@ -690,7 +739,6 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes 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 (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); @@ -701,36 +749,29 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &at return {}; } -/** - * Return a virtual array for a stored attribute, or a single value virtual array with the default - * 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 AttributeIDRef &attribute_id, - const CustomDataType data_type, - const void *default_value) const +GVArray 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(attribute_id); if (!attribute) { const int domain_size = this->size_; - return std::make_unique<GVArray_For_SingleValue>( + return GVArray::ForSingle( *type, domain_size, (default_value == nullptr) ? type->default_value() : default_value); } if (attribute->type() == *type) { - return std::make_unique<GVArray_For_GSpan>(*attribute); + return GVArray::ForSpan(*attribute); } - const blender::nodes::DataTypeConversions &conversions = - blender::nodes::get_implicit_type_conversions(); - return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type); + const blender::bke::DataTypeConversions &conversions = + blender::bke::get_implicit_type_conversions(); + return conversions.try_convert(GVArray::ForSpan(*attribute), *type); } 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 (custom_data_layer_matches_attribute_id(layer, attribute_id)) { const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type); @@ -777,18 +818,18 @@ void CustomDataAttributes::reallocate(const int size) CustomData_realloc(&data, size); } +void CustomDataAttributes::clear() +{ + CustomData_free(&data, size_); + size_ = 0; +} + bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback callback, const AttributeDomain domain) const { for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) { AttributeMetaData meta_data{domain, (CustomDataType)layer.type}; - AttributeIDRef attribute_id; - if (layer.anonymous_id != nullptr) { - attribute_id = layer.anonymous_id; - } - else { - attribute_id = layer.name; - } + const AttributeIDRef attribute_id = attribute_id_from_custom_data_layer(layer); if (!callback(attribute_id, meta_data)) { return false; } @@ -796,6 +837,26 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call 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 /* -------------------------------------------------------------------- */ @@ -863,8 +924,8 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( return {}; } -std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_domain( - std::unique_ptr<blender::fn::GVArray> varray, +blender::fn::GVArray GeometryComponent::attribute_try_adapt_domain_impl( + const blender::fn::GVArray &varray, const AttributeDomain from_domain, const AttributeDomain to_domain) const { @@ -886,7 +947,7 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ const BuiltinAttributeProvider *builtin_provider = providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr); if (builtin_provider != nullptr) { - return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()}; + return builtin_provider->try_get_for_write(*this); } } for (const DynamicAttributesProvider *dynamic_provider : @@ -986,10 +1047,6 @@ Set<AttributeIDRef> GeometryComponent::attribute_ids() const return attributes; } -/** - * \return False if the callback explicitly returned false at any point, otherwise true, - * meaning the callback made it all the way through. - */ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callback) const { using namespace blender::bke; @@ -1051,15 +1108,15 @@ std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data( return result; } -static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type( - std::unique_ptr<blender::fn::GVArray> varray, const blender::fn::CPPType &to_type) +static blender::fn::GVArray try_adapt_data_type(blender::fn::GVArray varray, + const blender::fn::CPPType &to_type) { - const blender::nodes::DataTypeConversions &conversions = - blender::nodes::get_implicit_type_conversions(); + const blender::bke::DataTypeConversions &conversions = + blender::bke::get_implicit_type_conversions(); return conversions.try_convert(std::move(varray), to_type); } -std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read( +blender::fn::GVArray GeometryComponent::attribute_try_get_for_read( const AttributeIDRef &attribute_id, const AttributeDomain domain, const CustomDataType data_type) const @@ -1069,8 +1126,8 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r return {}; } - std::unique_ptr<blender::fn::GVArray> varray = std::move(attribute.varray); - if (domain != ATTR_DOMAIN_AUTO && attribute.domain != domain) { + blender::fn::GVArray varray = std::move(attribute.varray); + if (!ELEM(domain, ATTR_DOMAIN_AUTO, attribute.domain)) { varray = this->attribute_try_adapt_domain(std::move(varray), attribute.domain, domain); if (!varray) { return {}; @@ -1079,7 +1136,7 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r const blender::fn::CPPType *cpp_type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); - if (varray->type() != *cpp_type) { + if (varray.type() != *cpp_type) { varray = try_adapt_data_type(std::move(varray), *cpp_type); if (!varray) { return {}; @@ -1089,7 +1146,7 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r return varray; } -std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read( +blender::fn::GVArray GeometryComponent::attribute_try_get_for_read( const AttributeIDRef &attribute_id, const AttributeDomain domain) const { if (!this->attribute_domain_supported(domain)) { @@ -1117,22 +1174,20 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read( } const blender::fn::CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type); BLI_assert(type != nullptr); - if (attribute.varray->type() == *type) { + if (attribute.varray.type() == *type) { return attribute; } - const blender::nodes::DataTypeConversions &conversions = - blender::nodes::get_implicit_type_conversions(); + const blender::bke::DataTypeConversions &conversions = + blender::bke::get_implicit_type_conversions(); return {conversions.try_convert(std::move(attribute.varray), *type), attribute.domain}; } -std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read( - const AttributeIDRef &attribute_id, - const AttributeDomain domain, - const CustomDataType data_type, - const void *default_value) const +blender::fn::GVArray GeometryComponent::attribute_get_for_read(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_id, domain, data_type); + blender::fn::GVArray varray = this->attribute_try_get_for_read(attribute_id, domain, data_type); if (varray) { return varray; } @@ -1141,11 +1196,10 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read default_value = type->default_value(); } const int domain_size = this->attribute_domain_size(domain); - return std::make_unique<blender::fn::GVArray_For_SingleValue>(*type, domain_size, default_value); + return blender::fn::GVArray::ForSingle(*type, domain_size, default_value); } -class GVMutableAttribute_For_OutputAttribute - : public blender::fn::GVMutableArray_For_GMutableSpan { +class GVMutableAttribute_For_OutputAttribute : public blender::fn::GVArrayImpl_For_GSpan { public: GeometryComponent *component; std::string attribute_name; @@ -1154,7 +1208,7 @@ class GVMutableAttribute_For_OutputAttribute GVMutableAttribute_For_OutputAttribute(GMutableSpan data, GeometryComponent &component, const AttributeIDRef &attribute_id) - : blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component) + : blender::fn::GVArrayImpl_For_GSpan(data), component(&component) { if (attribute_id.is_named()) { this->attribute_name = attribute_id.name(); @@ -1180,7 +1234,8 @@ static void save_output_attribute(OutputAttribute &output_attribute) using namespace blender::bke; GVMutableAttribute_For_OutputAttribute &varray = - dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray()); + dynamic_cast<GVMutableAttribute_For_OutputAttribute &>( + *output_attribute.varray().get_implementation()); GeometryComponent &component = *varray.component; AttributeIDRef attribute_id; @@ -1208,10 +1263,24 @@ static void save_output_attribute(OutputAttribute &output_attribute) 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); + write_attribute.varray.set_by_relocate(i, buffer); + } + if (write_attribute.tag_modified_fn) { + write_attribute.tag_modified_fn(); } } +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, @@ -1229,7 +1298,7 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, const CPPType *cpp_type = custom_data_type_to_cpp_type(data_type); BLI_assert(cpp_type != nullptr); - const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions(); + const DataTypeConversions &conversions = get_implicit_type_conversions(); if (component.attribute_is_builtin(attribute_id)) { const StringRef attribute_name = attribute_id.name(); @@ -1237,9 +1306,9 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, if (!attribute) { if (default_value) { const int64_t domain_size = component.attribute_domain_size(domain); - const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value}; - component.attribute_try_create_builtin(attribute_name, - AttributeInitVArray(&default_varray)); + component.attribute_try_create_builtin( + attribute_name, + AttributeInitVArray(GVArray::ForSingleRef(*cpp_type, domain_size, default_value))); } else { component.attribute_try_create_builtin(attribute_name, AttributeInitDefault()); @@ -1254,14 +1323,20 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, /* Builtin attribute is on different domain. */ return {}; } - GVMutableArrayPtr varray = std::move(attribute.varray); - if (varray->type() == *cpp_type) { + GVMutableArray 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); @@ -1269,9 +1344,11 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, 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_id, domain, data_type, AttributeInitVArray(&default_varray)); + attribute_id, + domain, + data_type, + AttributeInitVArray(GVArray::ForSingleRef(*cpp_type, domain_size, default_value))); } else { component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault()); @@ -1283,9 +1360,13 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, return {}; } } - if (attribute.domain == domain && attribute.varray->type() == *cpp_type) { + 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 @@ -1298,11 +1379,11 @@ static OutputAttribute create_output_attribute(GeometryComponent &component, } else { /* Fill the temporary array with values from the existing attribute. */ - GVArrayPtr old_varray = component.attribute_get_for_read( + GVArray old_varray = component.attribute_get_for_read( attribute_id, domain, data_type, default_value); - old_varray->materialize_to_uninitialized(IndexRange(domain_size), data); + old_varray.materialize_to_uninitialized(IndexRange(domain_size), data); } - GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>( + GVMutableArray varray = GVMutableArray::For<GVMutableAttribute_For_OutputAttribute>( GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id); return OutputAttribute(std::move(varray), domain, save_output_attribute, true); @@ -1326,27 +1407,31 @@ OutputAttribute GeometryComponent::attribute_try_get_for_output_only( namespace blender::bke { -const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context, - IndexMask UNUSED(mask), - ResourceScope &scope) const +GVArray GeometryFieldInput::get_varray_for_context(const fn::FieldContext &context, + IndexMask mask, + ResourceScope &UNUSED(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 this->get_varray_for_context(component, domain, mask); } - return nullptr; + return {}; +} + +GVArray AttributeFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const +{ + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + return component.attribute_try_get_for_read(name_, domain, data_type); } std::string AttributeFieldInput::socket_inspection_name() const { std::stringstream ss; - ss << TIP_("Attribute: ") << name_; + ss << '"' << name_ << '"' << TIP_(" attribute from geometry"); return ss.str(); } @@ -1363,25 +1448,62 @@ bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const return false; } -const GVArray *AnonymousAttributeFieldInput::get_varray_for_context( - const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const +static StringRef get_random_id_attribute_name(const AttributeDomain domain) { - 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)); + switch (domain) { + case ATTR_DOMAIN_POINT: + case ATTR_DOMAIN_INSTANCE: + return "id"; + default: + return ""; } - return nullptr; +} + +GVArray IDAttributeFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const +{ + + const StringRef name = get_random_id_attribute_name(domain); + GVArray attribute = component.attribute_try_get_for_read(name, domain, CD_PROP_INT32); + if (attribute) { + BLI_assert(attribute.size() == component.attribute_domain_size(domain)); + return attribute; + } + + /* Use the index as the fallback if no random ID attribute exists. */ + return fn::IndexFieldInput::get_index_varray(mask); +} + +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; +} + +GVArray AnonymousAttributeFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask UNUSED(mask)) const +{ + const CustomDataType data_type = cpp_type_to_custom_data_type(*type_); + return component.attribute_try_get_for_read(anonymous_id_.get(), domain, data_type); } std::string AnonymousAttributeFieldInput::socket_inspection_name() const { std::stringstream ss; - ss << TIP_("Anonymous Attribute: ") << debug_name_; + ss << '"' << debug_name_ << '"' << TIP_(" from ") << producer_name_; return ss.str(); } @@ -1400,3 +1522,5 @@ bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const } } // namespace blender::bke + +/** \} */ diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 261cb26d4e5..2cd128081eb 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -24,9 +24,6 @@ namespace blender::bke { -using fn::GVArrayPtr; -using fn::GVMutableArrayPtr; - /** * Utility to group together multiple functions that are used to access custom data on geometry * components in a generic way. @@ -86,8 +83,8 @@ class BuiltinAttributeProvider { { } - virtual GVArrayPtr try_get_for_read(const GeometryComponent &component) const = 0; - virtual GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const = 0; + virtual GVArray try_get_for_read(const 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; @@ -164,7 +161,7 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { bool try_create(GeometryComponent &component, const AttributeIDRef &attribute_id, - const AttributeDomain domain, + AttributeDomain domain, const CustomDataType data_type, const AttributeInit &initializer) const final; @@ -177,24 +174,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; @@ -206,8 +185,8 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider { */ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider { private: - using AsReadAttribute = GVArrayPtr (*)(const void *data, const int domain_size); - using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArray (*)(const void *data, int domain_size); + using AsWriteAttribute = GVMutableArray (*)(void *data, int domain_size); const AttributeDomain domain_; const CustomDataType attribute_type_; const CustomDataType stored_type_; @@ -245,10 +224,13 @@ 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); - using AsWriteAttribute = GVMutableArrayPtr (*)(void *data, const int domain_size); + using AsReadAttribute = GVArray (*)(const void *data, int domain_size); + using AsWriteAttribute = GVMutableArray (*)(void *data, int domain_size); using UpdateOnRead = void (*)(const GeometryComponent &component); using UpdateOnWrite = void (*)(GeometryComponent &component); const CustomDataType stored_type_; @@ -256,6 +238,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, @@ -275,12 +258,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; + GVArray try_get_for_read(const 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 3aec646b024..ed2b4e4ab1b 100644 --- a/source/blender/blenkernel/intern/autoexec.c +++ b/source/blender/blenkernel/intern/autoexec.c @@ -36,10 +36,6 @@ #include "BKE_autoexec.h" /* own include */ -/** - * \param path: The path to check against. - * \return Success - */ bool BKE_autoexec_match(const char *path) { bPathCompare *path_cmp; diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c index 97f8bddc043..b914b0cdf66 100644 --- a/source/blender/blenkernel/intern/blender.c +++ b/source/blender/blenkernel/intern/blender.c @@ -71,7 +71,6 @@ UserDef U; /** \name Blender Free on Exit * \{ */ -/* only to be called on exit blender */ void BKE_blender_free(void) { /* samples are in a global list..., also sets G_MAIN->sound->sample NULL */ @@ -90,7 +89,6 @@ void BKE_blender_free(void) IMB_exit(); BKE_cachefiles_exit(); - BKE_images_exit(); DEG_free_node_types(); BKE_brush_system_exit(); @@ -273,10 +271,6 @@ static void userdef_free_addons(UserDef *userdef) BLI_listbase_clear(&userdef->addons); } -/** - * When loading a new userdef from file, - * or when exiting Blender. - */ void BKE_blender_userdef_data_free(UserDef *userdef, bool clear_fonts) { #define U BLI_STATIC_ASSERT(false, "Global 'U' not allowed, only use arguments passed in!") @@ -311,10 +305,6 @@ void BKE_blender_userdef_data_free(UserDef *userdef, bool clear_fonts) /** \name Blender Preferences (Application Templates) * \{ */ -/** - * Write U from userdef. - * This function defines which settings a template will override for the user preferences. - */ void BKE_blender_userdef_app_template_data_swap(UserDef *userdef_a, UserDef *userdef_b) { /* TODO: diff --git a/source/blender/blenkernel/intern/blender_copybuffer.c b/source/blender/blenkernel/intern/blender_copybuffer.c index 9c9f898afef..211d7f693d4 100644 --- a/source/blender/blenkernel/intern/blender_copybuffer.c +++ b/source/blender/blenkernel/intern/blender_copybuffer.c @@ -38,6 +38,7 @@ #include "BKE_blender_copybuffer.h" /* own include */ #include "BKE_blendfile.h" +#include "BKE_blendfile_link_append.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_layer.h" @@ -57,20 +58,17 @@ /** \name Copy/Paste `.blend`, partial saves. * \{ */ -void BKE_copybuffer_begin(Main *bmain_src) +void BKE_copybuffer_copy_begin(Main *bmain_src) { BKE_blendfile_write_partial_begin(bmain_src); } -void BKE_copybuffer_tag_ID(ID *id) +void BKE_copybuffer_copy_tag_ID(ID *id) { BKE_blendfile_write_partial_tag_ID(id, true); } -/** - * \return Success. - */ -bool BKE_copybuffer_save(Main *bmain_src, const char *filename, ReportList *reports) +bool BKE_copybuffer_copy_end(Main *bmain_src, const char *filename, ReportList *reports) { const int write_flags = 0; const eBLO_WritePathRemap remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE; @@ -82,46 +80,63 @@ bool BKE_copybuffer_save(Main *bmain_src, const char *filename, ReportList *repo return retval; } +/* Common helper for paste functions. */ +static void copybuffer_append(BlendfileLinkAppendContext *lapp_context, + Main *bmain, + ReportList *reports) +{ + /* Tag existing IDs in given `bmain_dst` as already existing. */ + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); + + BKE_blendfile_link(lapp_context, reports); + + /* Mark all library linked objects to be updated. */ + BKE_main_lib_objects_recalc_all(bmain); + IMB_colormanagement_check_file_config(bmain); + + /* Append, rather than linking */ + BKE_blendfile_append(lapp_context, reports); + + /* This must be unset, otherwise these object won't link into other scenes from this blend + * file. */ + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); + + /* Recreate dependency graph to include new objects. */ + DEG_relations_tag_update(bmain); +} + bool BKE_copybuffer_read(Main *bmain_dst, const char *libname, ReportList *reports, const uint64_t id_types_mask) { - BlendFileReadReport bf_reports = {.reports = reports}; - BlendHandle *bh = BLO_blendhandle_from_file(libname, &bf_reports); - if (bh == NULL) { - /* Error reports will have been made by BLO_blendhandle_from_file(). */ - return false; - } - /* Here appending/linking starts. */ + /* Note: No recursive append here (no `BLO_LIBLINK_APPEND_RECURSIVE`), external linked data + * should remain linked. */ const int flag = 0; const int id_tag_extra = 0; struct LibraryLink_Params liblink_params; BLO_library_link_params_init(&liblink_params, bmain_dst, flag, id_tag_extra); - Main *mainl = BLO_library_link_begin(&bh, libname, &liblink_params); - BLO_library_link_copypaste(mainl, bh, id_types_mask); - BLO_library_link_end(mainl, &bh, &liblink_params); - /* Mark all library linked objects to be updated. */ - BKE_main_lib_objects_recalc_all(bmain_dst); - IMB_colormanagement_check_file_config(bmain_dst); - /* Append, rather than linking. */ - Library *lib = BLI_findstring(&bmain_dst->libraries, libname, offsetof(Library, filepath_abs)); - BKE_library_make_local(bmain_dst, lib, NULL, true, false); - /* Important we unset, otherwise these object won't - * link into other scenes from this blend file. - */ - BKE_main_id_tag_all(bmain_dst, LIB_TAG_PRE_EXISTING, false); - BLO_blendhandle_close(bh); + + BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new( + &liblink_params); + BKE_blendfile_link_append_context_library_add(lapp_context, libname, NULL); + + const int num_pasted = BKE_blendfile_link_append_context_item_idtypes_from_library_add( + lapp_context, reports, id_types_mask, 0); + if (num_pasted == BLENDFILE_LINK_APPEND_INVALID) { + BKE_blendfile_link_append_context_free(lapp_context); + return false; + } + + copybuffer_append(lapp_context, bmain_dst, reports); + + BKE_blendfile_link_append_context_free(lapp_context); return true; } -/** - * \return Number of IDs directly pasted from the buffer - * (does not includes indirectly pulled out ones). - */ int BKE_copybuffer_paste(bContext *C, const char *libname, - const short flag, + const int flag, ReportList *reports, const uint64_t id_types_mask) { @@ -129,59 +144,31 @@ int BKE_copybuffer_paste(bContext *C, Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = CTX_wm_view3d(C); /* may be NULL. */ - Main *mainl = NULL; - Library *lib; - BlendHandle *bh; const int id_tag_extra = 0; - BlendFileReadReport bf_reports = {.reports = reports}; - bh = BLO_blendhandle_from_file(libname, &bf_reports); - - if (bh == NULL) { - /* error reports will have been made by BLO_blendhandle_from_file() */ - return 0; - } + /* Note: No recursive append here, external linked data should remain linked. */ + BLI_assert((flag & BLO_LIBLINK_APPEND_RECURSIVE) == 0); - BKE_view_layer_base_deselect_all(view_layer); - - /* tag everything, all untagged data can be made local - * its also generally useful to know what is new - * - * take extra care BKE_main_id_flag_all(bmain, LIB_TAG_PRE_EXISTING, false) is called after! */ - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); - - /* here appending/linking starts */ struct LibraryLink_Params liblink_params; BLO_library_link_params_init_with_context( &liblink_params, bmain, flag, id_tag_extra, scene, view_layer, v3d); - mainl = BLO_library_link_begin(&bh, libname, &liblink_params); - - const int num_pasted = BLO_library_link_copypaste(mainl, bh, id_types_mask); - BLO_library_link_end(mainl, &bh, &liblink_params); + BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new( + &liblink_params); + BKE_blendfile_link_append_context_library_add(lapp_context, libname, NULL); - /* mark all library linked objects to be updated */ - BKE_main_lib_objects_recalc_all(bmain); - IMB_colormanagement_check_file_config(bmain); - - /* append, rather than linking */ - lib = BLI_findstring(&bmain->libraries, libname, offsetof(Library, filepath_abs)); - BKE_library_make_local(bmain, lib, NULL, true, false); - - /* important we unset, otherwise these object won't - * link into other scenes from this blend file */ - BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, false); - - /* recreate dependency graph to include new objects */ - DEG_relations_tag_update(bmain); + const int num_pasted = BKE_blendfile_link_append_context_item_idtypes_from_library_add( + lapp_context, reports, id_types_mask, 0); + if (num_pasted == BLENDFILE_LINK_APPEND_INVALID) { + BKE_blendfile_link_append_context_free(lapp_context); + return 0; + } - /* Tag update the scene to flush base collection settings, since the new object is added to a - * new (active) collection, not its original collection, thus need recalculation. */ - DEG_id_tag_update(&scene->id, 0); + BKE_view_layer_base_deselect_all(view_layer); - BLO_blendhandle_close(bh); - /* remove library... */ + copybuffer_append(lapp_context, bmain, reports); + BKE_blendfile_link_append_context_free(lapp_context); return num_pasted; } diff --git a/source/blender/blenkernel/intern/blender_undo.c b/source/blender/blenkernel/intern/blender_undo.c index 411ece21599..6c443a94def 100644 --- a/source/blender/blenkernel/intern/blender_undo.c +++ b/source/blender/blenkernel/intern/blender_undo.c @@ -68,7 +68,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, bContext *C) { Main *bmain = CTX_data_main(C); - char mainstr[sizeof(bmain->name)]; + char mainstr[sizeof(bmain->filepath)]; int success = 0, fileflags; BLI_strncpy(mainstr, BKE_main_blendfile_path(bmain), sizeof(mainstr)); /* temporal store */ @@ -101,7 +101,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, /* Restore, bmain has been re-allocated. */ bmain = CTX_data_main(C); - BLI_strncpy(bmain->name, mainstr, sizeof(bmain->name)); + STRNCPY(bmain->filepath, mainstr); G.fileflags = fileflags; if (success) { diff --git a/source/blender/blenkernel/intern/blender_user_menu.c b/source/blender/blenkernel/intern/blender_user_menu.c index edd89357fd5..b186d376e52 100644 --- a/source/blender/blenkernel/intern/blender_user_menu.c +++ b/source/blender/blenkernel/intern/blender_user_menu.c @@ -110,3 +110,5 @@ void BKE_blender_user_menu_item_free_list(ListBase *lb) } BLI_listbase_clear(lb); } + +/** \} */ diff --git a/source/blender/blenkernel/intern/blendfile.c b/source/blender/blenkernel/intern/blendfile.c index 6957f9b5a69..6ae19c8036f 100644 --- a/source/blender/blenkernel/intern/blendfile.c +++ b/source/blender/blenkernel/intern/blendfile.c @@ -78,7 +78,9 @@ /** \name High Level `.blend` file read/write. * \{ */ -static bool clean_paths_visit_cb(void *UNUSED(userdata), char *path_dst, const char *path_src) +static bool foreach_path_clean_cb(BPathForeachPathData *UNUSED(bpath_data), + char *path_dst, + const char *path_src) { strcpy(path_dst, path_src); BLI_path_slash_native(path_dst); @@ -86,13 +88,16 @@ static bool clean_paths_visit_cb(void *UNUSED(userdata), char *path_dst, const c } /* make sure path names are correct for OS */ -static void clean_paths(Main *main) +static void clean_paths(Main *bmain) { - Scene *scene; - - BKE_bpath_traverse_main(main, clean_paths_visit_cb, BKE_BPATH_TRAVERSE_SKIP_MULTIFILE, NULL); - - for (scene = main->scenes.first; scene; scene = scene->id.next) { + BKE_bpath_foreach_path_main(&(BPathForeachPathData){ + .bmain = bmain, + .callback_function = foreach_path_clean_cb, + .flag = BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE, + .user_data = NULL, + }); + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { BLI_path_slash_native(scene->r.pic); } } @@ -251,7 +256,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; } @@ -347,7 +352,7 @@ static void setup_app_data(bContext *C, /* 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 (!USER_EXPERIMENTAL_TEST(&U, no_proxy_to_override_conversion)) { + if (mode != LOAD_UNDO && !USER_EXPERIMENTAL_TEST(&U, no_proxy_to_override_conversion)) { BKE_lib_override_library_main_proxy_convert(bmain, reports); } @@ -355,12 +360,12 @@ static void setup_app_data(bContext *C, /* startup.blend or recovered startup */ if (is_startup) { - bmain->name[0] = '\0'; + bmain->filepath[0] = '\0'; } else if (recover) { - /* In case of autosave or quit.blend, use original filename instead. */ + /* In case of autosave or quit.blend, use original filepath instead. */ bmain->recovered = 1; - BLI_strncpy(bmain->name, bfd->filename, FILE_MAX); + STRNCPY(bmain->filepath, bfd->filepath); } /* baseflags, groups, make depsgraph, etc */ @@ -447,14 +452,6 @@ static void handle_subversion_warning(Main *main, BlendFileReadReport *reports) } } -/** - * Shared setup function that makes the data from `bfd` into the current blend file, - * replacing the contents of #G.main. - * This uses the bfd #BKE_blendfile_read and similarly named functions. - * - * This is done in a separate step so the caller may perform actions after it is known the file - * loaded correctly but before the file replaces the existing blend file contents. - */ void BKE_blendfile_read_setup_ex(bContext *C, BlendFileData *bfd, const struct BlendFileReadParams *params, @@ -480,9 +477,6 @@ void BKE_blendfile_read_setup(bContext *C, BKE_blendfile_read_setup_ex(C, bfd, params, reports, false, NULL); } -/** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. - */ struct BlendFileData *BKE_blendfile_read(const char *filepath, const struct BlendFileReadParams *params, BlendFileReadReport *reports) @@ -502,9 +496,6 @@ struct BlendFileData *BKE_blendfile_read(const char *filepath, return bfd; } -/** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. - */ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, int filelength, const struct BlendFileReadParams *params, @@ -520,10 +511,6 @@ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, return bfd; } -/** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. - * \note `memfile` is the undo buffer. - */ struct BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain, struct MemFile *memfile, const struct BlendFileReadParams *params, @@ -546,10 +533,6 @@ struct BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain, return bfd; } -/** - * Utility to make a file 'empty' used for startup to optionally give an empty file. - * Handy for tests. - */ void BKE_blendfile_read_make_empty(bContext *C) { Main *bmain = CTX_data_main(C); @@ -568,7 +551,6 @@ void BKE_blendfile_read_make_empty(bContext *C) FOREACH_MAIN_LISTBASE_END; } -/* only read the userdef from a .blend */ UserDef *BKE_blendfile_userdef_read(const char *filepath, ReportList *reports) { BlendFileData *bfd; @@ -671,10 +653,6 @@ UserDef *BKE_blendfile_userdef_from_defaults(void) return userdef; } -/** - * Only write the userdef in a .blend - * \return success - */ bool BKE_blendfile_userdef_write(const char *filepath, ReportList *reports) { Main *mainb = MEM_callocN(sizeof(Main), "empty main"); @@ -695,13 +673,6 @@ bool BKE_blendfile_userdef_write(const char *filepath, ReportList *reports) return ok; } -/** - * Only write the userdef in a .blend, merging with the existing blend file. - * \return success - * - * \note In the future we should re-evaluate user preferences, - * possibly splitting out system/hardware specific prefs. - */ bool BKE_blendfile_userdef_write_app_template(const char *filepath, ReportList *reports) { /* if it fails, overwrite is OK. */ @@ -872,10 +843,6 @@ static void blendfile_write_partial_cb(void *UNUSED(handle), Main *UNUSED(bmain) } } -/** - * \param remap_mode: Choose the kind of path remapping or none #eBLO_WritePathRemap. - * \return Success. - */ bool BKE_blendfile_write_partial(Main *bmain_src, const char *filepath, const int write_flags, @@ -887,11 +854,12 @@ bool BKE_blendfile_write_partial(Main *bmain_src, int a, retval; void *path_list_backup = NULL; - const int path_list_flag = (BKE_BPATH_TRAVERSE_SKIP_LIBRARY | BKE_BPATH_TRAVERSE_SKIP_MULTIFILE); + const eBPathForeachFlag path_list_flag = (BKE_BPATH_FOREACH_PATH_SKIP_LINKED | + BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE); /* This is needed to be able to load that file as a real one later - * (otherwise main->name will not be set at read time). */ - BLI_strncpy(bmain_dst->name, bmain_src->name, sizeof(bmain_dst->name)); + * (otherwise `main->filepath` will not be set at read time). */ + STRNCPY(bmain_dst->filepath, bmain_src->filepath); BLO_main_expander(blendfile_write_partial_cb); BLO_expand_main(NULL, bmain_src); diff --git a/source/blender/blenkernel/intern/blendfile_link_append.c b/source/blender/blenkernel/intern/blendfile_link_append.c new file mode 100644 index 00000000000..9b3f4c2fae8 --- /dev/null +++ b/source/blender/blenkernel/intern/blendfile_link_append.c @@ -0,0 +1,1649 @@ +/* + * 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 + * + * High level `.blend` file link/append code, + * including linking/appending several IDs from different libraries, handling instantiations of + * collections/objects/object-data in current scene. + */ + +#include <stdlib.h> +#include <string.h> + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "DNA_ID.h" +#include "DNA_collection_types.h" +#include "DNA_key_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "BLI_bitmap.h" +#include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_idtype.h" +#include "BKE_key.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_override.h" +#include "BKE_lib_query.h" +#include "BKE_lib_remap.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_report.h" +#include "BKE_rigidbody.h" +#include "BKE_scene.h" + +#include "BKE_blendfile_link_append.h" + +#include "BLO_readfile.h" +#include "BLO_writefile.h" + +static CLG_LogRef LOG = {"bke.blendfile_link_append"}; + +/* -------------------------------------------------------------------- */ +/** \name Link/append context implementation and public management API. + * \{ */ + +typedef struct BlendfileLinkAppendContextItem { + /** Name of the ID (without the heading two-chars IDcode). */ + char *name; + /** All libs (from BlendfileLinkAppendContext.libraries) to try to load this ID from. */ + BLI_bitmap *libraries; + /** ID type. */ + short idcode; + + /** Type of action to perform on this item, and general status tag information. + * NOTE: Mostly used by append post-linking processing. */ + char action; + char tag; + + /** Newly linked ID (NULL until it has been successfully linked). */ + ID *new_id; + /** Library ID from which the #new_id has been linked (NULL until it has been successfully + * linked). */ + Library *source_library; + /** Opaque user data pointer. */ + void *userdata; +} BlendfileLinkAppendContextItem; + +/* A blendfile library entry in the `libraries` list of #BlendfileLinkAppendContext. */ +typedef struct BlendfileLinkAppendContextLibrary { + char *path; /* Absolute .blend file path. */ + BlendHandle *blo_handle; /* Blend file handle, if any. */ + bool blo_handle_is_owned; /* Whether the blend file handle is owned, or borrowed. */ + /* The blendfile report associated with the `blo_handle`, if owned. */ + BlendFileReadReport bf_reports; +} BlendfileLinkAppendContextLibrary; + +typedef struct BlendfileLinkAppendContext { + /** List of library paths to search IDs in. */ + LinkNodePair libraries; + /** List of all ID to try to link from #libraries. */ + LinkNodePair items; + int num_libraries; + int num_items; + /** Linking/appending parameters. Including `bmain`, `scene`, `viewlayer` and `view3d`. */ + LibraryLink_Params *params; + + /** Allows to easily find an existing items from an ID pointer. */ + GHash *new_id_to_item; + + /** Runtime info used by append code to manage re-use of already appended matching IDs. */ + GHash *library_weak_reference_mapping; + + /** Embedded blendfile and its size, if needed. */ + const void *blendfile_mem; + size_t blendfile_memsize; + + /** Internal 'private' data */ + MemArena *memarena; +} BlendfileLinkAppendContext; + +typedef struct BlendfileLinkAppendContextCallBack { + BlendfileLinkAppendContext *lapp_context; + BlendfileLinkAppendContextItem *item; + ReportList *reports; + +} BlendfileLinkAppendContextCallBack; + +/* Actions to apply to an item (i.e. linked ID). */ +enum { + LINK_APPEND_ACT_UNSET = 0, + LINK_APPEND_ACT_KEEP_LINKED, + LINK_APPEND_ACT_REUSE_LOCAL, + LINK_APPEND_ACT_MAKE_LOCAL, + LINK_APPEND_ACT_COPY_LOCAL, +}; + +/* Various status info about an item (i.e. linked ID). */ +enum { + /* An indirectly linked ID. */ + LINK_APPEND_TAG_INDIRECT = 1 << 0, +}; + +static BlendHandle *link_append_context_library_blohandle_ensure( + BlendfileLinkAppendContext *lapp_context, + BlendfileLinkAppendContextLibrary *lib_context, + ReportList *reports) +{ + if (reports != NULL) { + lib_context->bf_reports.reports = reports; + } + + char *libname = lib_context->path; + BlendHandle *blo_handle = lib_context->blo_handle; + if (blo_handle == NULL) { + if (STREQ(libname, BLO_EMBEDDED_STARTUP_BLEND)) { + blo_handle = BLO_blendhandle_from_memory(lapp_context->blendfile_mem, + (int)lapp_context->blendfile_memsize, + &lib_context->bf_reports); + } + else { + blo_handle = BLO_blendhandle_from_file(libname, &lib_context->bf_reports); + } + lib_context->blo_handle = blo_handle; + lib_context->blo_handle_is_owned = true; + } + + return blo_handle; +} + +static void link_append_context_library_blohandle_release( + BlendfileLinkAppendContext *UNUSED(lapp_context), + BlendfileLinkAppendContextLibrary *lib_context) +{ + if (lib_context->blo_handle_is_owned && lib_context->blo_handle != NULL) { + BLO_blendhandle_close(lib_context->blo_handle); + lib_context->blo_handle = NULL; + } +} + +BlendfileLinkAppendContext *BKE_blendfile_link_append_context_new(LibraryLink_Params *params) +{ + MemArena *ma = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + BlendfileLinkAppendContext *lapp_context = BLI_memarena_calloc(ma, sizeof(*lapp_context)); + + lapp_context->params = params; + lapp_context->memarena = ma; + + return lapp_context; +} + +void BKE_blendfile_link_append_context_free(BlendfileLinkAppendContext *lapp_context) +{ + if (lapp_context->new_id_to_item != NULL) { + BLI_ghash_free(lapp_context->new_id_to_item, NULL, NULL); + } + + for (LinkNode *liblink = lapp_context->libraries.list; liblink != NULL; + liblink = liblink->next) { + BlendfileLinkAppendContextLibrary *lib_context = liblink->link; + link_append_context_library_blohandle_release(lapp_context, lib_context); + } + + BLI_assert(lapp_context->library_weak_reference_mapping == NULL); + + BLI_memarena_free(lapp_context->memarena); +} + +void BKE_blendfile_link_append_context_flag_set(BlendfileLinkAppendContext *lapp_context, + const int flag, + const bool do_set) +{ + if (do_set) { + lapp_context->params->flag |= flag; + } + else { + lapp_context->params->flag &= ~flag; + } +} + +void BKE_blendfile_link_append_context_embedded_blendfile_set( + BlendfileLinkAppendContext *lapp_context, const void *blendfile_mem, int blendfile_memsize) +{ + BLI_assert_msg(lapp_context->blendfile_mem == NULL, + "Please explicitly clear reference to an embedded blender memfile before " + "setting a new one"); + lapp_context->blendfile_mem = blendfile_mem; + lapp_context->blendfile_memsize = (size_t)blendfile_memsize; +} + +void BKE_blendfile_link_append_context_embedded_blendfile_clear( + BlendfileLinkAppendContext *lapp_context) +{ + lapp_context->blendfile_mem = NULL; + lapp_context->blendfile_memsize = 0; +} + +void BKE_blendfile_link_append_context_library_add(BlendfileLinkAppendContext *lapp_context, + const char *libname, + BlendHandle *blo_handle) +{ + BLI_assert(lapp_context->items.list == NULL); + + BlendfileLinkAppendContextLibrary *lib_context = BLI_memarena_calloc(lapp_context->memarena, + sizeof(*lib_context)); + + size_t len = strlen(libname) + 1; + char *libpath = BLI_memarena_alloc(lapp_context->memarena, len); + BLI_strncpy(libpath, libname, len); + + lib_context->path = libpath; + lib_context->blo_handle = blo_handle; + lib_context->blo_handle_is_owned = (blo_handle == NULL); + + BLI_linklist_append_arena(&lapp_context->libraries, lib_context, lapp_context->memarena); + lapp_context->num_libraries++; +} + +BlendfileLinkAppendContextItem *BKE_blendfile_link_append_context_item_add( + BlendfileLinkAppendContext *lapp_context, + const char *idname, + const short idcode, + void *userdata) +{ + BlendfileLinkAppendContextItem *item = BLI_memarena_calloc(lapp_context->memarena, + sizeof(*item)); + size_t len = strlen(idname) + 1; + + item->name = BLI_memarena_alloc(lapp_context->memarena, len); + BLI_strncpy(item->name, idname, len); + item->idcode = idcode; + item->libraries = BLI_BITMAP_NEW_MEMARENA(lapp_context->memarena, lapp_context->num_libraries); + + item->new_id = NULL; + item->action = LINK_APPEND_ACT_UNSET; + item->userdata = userdata; + + BLI_linklist_append_arena(&lapp_context->items, item, lapp_context->memarena); + lapp_context->num_items++; + + return item; +} + +int BKE_blendfile_link_append_context_item_idtypes_from_library_add( + BlendfileLinkAppendContext *lapp_context, + ReportList *reports, + const uint64_t id_types_filter, + const int library_index) +{ + int id_num = 0; + int id_code_iter = 0; + short id_code; + + LinkNode *lib_context_link = BLI_linklist_find(lapp_context->libraries.list, library_index); + BlendfileLinkAppendContextLibrary *lib_context = lib_context_link->link; + BlendHandle *blo_handle = link_append_context_library_blohandle_ensure( + lapp_context, lib_context, reports); + + if (blo_handle == NULL) { + return BLENDFILE_LINK_APPEND_INVALID; + } + + const bool use_assets_only = (lapp_context->params->flag & FILE_ASSETS_ONLY) != 0; + + while ((id_code = BKE_idtype_idcode_iter_step(&id_code_iter))) { + if (!BKE_idtype_idcode_is_linkable(id_code) || + (id_types_filter != 0 && + (BKE_idtype_idcode_to_idfilter(id_code) & id_types_filter) == 0)) { + continue; + } + + int id_names_num; + LinkNode *id_names_list = BLO_blendhandle_get_datablock_names( + blo_handle, id_code, use_assets_only, &id_names_num); + + for (LinkNode *link_next = NULL; id_names_list != NULL; id_names_list = link_next) { + link_next = id_names_list->next; + + char *id_name = id_names_list->link; + BlendfileLinkAppendContextItem *item = BKE_blendfile_link_append_context_item_add( + lapp_context, id_name, id_code, NULL); + BKE_blendfile_link_append_context_item_library_index_enable( + lapp_context, item, library_index); + + MEM_freeN(id_name); + MEM_freeN(id_names_list); + } + + id_num += id_names_num; + } + + return id_num; +} + +void BKE_blendfile_link_append_context_item_library_index_enable( + BlendfileLinkAppendContext *UNUSED(lapp_context), + BlendfileLinkAppendContextItem *item, + const int library_index) +{ + BLI_BITMAP_ENABLE(item->libraries, library_index); +} + +bool BKE_blendfile_link_append_context_is_empty(struct BlendfileLinkAppendContext *lapp_context) +{ + return lapp_context->num_items == 0; +} + +void *BKE_blendfile_link_append_context_item_userdata_get( + BlendfileLinkAppendContext *UNUSED(lapp_context), BlendfileLinkAppendContextItem *item) +{ + return item->userdata; +} + +ID *BKE_blendfile_link_append_context_item_newid_get( + BlendfileLinkAppendContext *UNUSED(lapp_context), BlendfileLinkAppendContextItem *item) +{ + return item->new_id; +} + +short BKE_blendfile_link_append_context_item_idcode_get( + struct BlendfileLinkAppendContext *UNUSED(lapp_context), + struct BlendfileLinkAppendContextItem *item) +{ + return item->idcode; +} + +void BKE_blendfile_link_append_context_item_foreach( + struct BlendfileLinkAppendContext *lapp_context, + BKE_BlendfileLinkAppendContexteItemFunction callback_function, + const eBlendfileLinkAppendForeachItemFlag flag, + void *userdata) +{ + for (LinkNode *itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + + if ((flag & BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_DIRECT) == 0 && + (item->tag & LINK_APPEND_TAG_INDIRECT) == 0) { + continue; + } + if ((flag & BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_INDIRECT) == 0 && + (item->tag & LINK_APPEND_TAG_INDIRECT) != 0) { + continue; + } + + if (!callback_function(lapp_context, item, userdata)) { + break; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Library link/append helper functions. + * + * \{ */ + +/* Struct gathering all required data to handle instantiation of loose data-blocks. */ +typedef struct LooseDataInstantiateContext { + BlendfileLinkAppendContext *lapp_context; + + /* The collection in which to add loose collections/objects. */ + Collection *active_collection; +} LooseDataInstantiateContext; + +static bool object_in_any_scene(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Scene *, sce, &bmain->scenes) { + if (BKE_scene_object_find(sce, ob)) { + return true; + } + } + + return false; +} + +static bool object_in_any_collection(Main *bmain, Object *ob) +{ + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if (BKE_collection_has_object(collection, ob)) { + return true; + } + } + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (scene->master_collection != NULL && + BKE_collection_has_object(scene->master_collection, ob)) { + return true; + } + } + + return false; +} + +static bool collection_instantiated_by_any_object(Main *bmain, Collection *collection) +{ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_EMPTY && ob->instance_collection == collection) { + return true; + } + } + return false; +} + +static ID *loose_data_instantiate_process_check(LooseDataInstantiateContext *instantiate_context, + BlendfileLinkAppendContextItem *item) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + /* In linking case, we always want to handle instantiation. */ + if (lapp_context->params->flag & FILE_LINK) { + return item->new_id; + } + + /* We consider that if we either kept it linked, or re-used already local data, instantiation + * status of those should not be modified. */ + if (!ELEM(item->action, LINK_APPEND_ACT_COPY_LOCAL, LINK_APPEND_ACT_MAKE_LOCAL)) { + return NULL; + } + + ID *id = item->new_id; + if (id == NULL) { + return NULL; + } + + if (item->action == LINK_APPEND_ACT_COPY_LOCAL) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + return NULL; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; + } + + BLI_assert(!ID_IS_LINKED(id)); + return id; +} + +static void loose_data_instantiate_ensure_active_collection( + LooseDataInstantiateContext *instantiate_context) +{ + + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + Main *bmain = instantiate_context->lapp_context->params->bmain; + Scene *scene = instantiate_context->lapp_context->params->context.scene; + ViewLayer *view_layer = instantiate_context->lapp_context->params->context.view_layer; + + /* Find or add collection as needed. */ + if (instantiate_context->active_collection == NULL) { + if (lapp_context->params->flag & FILE_ACTIVE_COLLECTION) { + LayerCollection *lc = BKE_layer_collection_get_active(view_layer); + instantiate_context->active_collection = lc->collection; + } + else { + if (lapp_context->params->flag & FILE_LINK) { + instantiate_context->active_collection = BKE_collection_add( + bmain, scene->master_collection, DATA_("Linked Data")); + } + else { + instantiate_context->active_collection = BKE_collection_add( + bmain, scene->master_collection, DATA_("Appended Data")); + } + } + } +} + +static void loose_data_instantiate_object_base_instance_init(Main *bmain, + Collection *collection, + Object *ob, + ViewLayer *view_layer, + const View3D *v3d, + const int flag, + bool set_active) +{ + /* Auto-select and appending. */ + if ((flag & FILE_AUTOSELECT) && ((flag & FILE_LINK) == 0)) { + /* While in general the object should not be manipulated, + * when the user requests the object to be selected, ensure it's visible and selectable. */ + ob->visibility_flag &= ~(OB_HIDE_VIEWPORT | OB_HIDE_SELECT); + } + + BKE_collection_object_add(bmain, collection, ob); + + Base *base = BKE_view_layer_base_find(view_layer, ob); + + if (v3d != NULL) { + base->local_view_bits |= v3d->local_view_uuid; + } + + if (flag & FILE_AUTOSELECT) { + /* All objects that use #FILE_AUTOSELECT must be selectable (unless linking data). */ + BLI_assert((base->flag & BASE_SELECTABLE) || (flag & FILE_LINK)); + if (base->flag & BASE_SELECTABLE) { + base->flag |= BASE_SELECTED; + } + } + + if (set_active) { + view_layer->basact = base; + } + + BKE_scene_object_base_flag_sync_from_base(base); +} + +/* Tag obdata that actually need to be instantiated (those referenced by an object do not, since + * the object will be instantiated instead if needed. */ +static void loose_data_instantiate_obdata_preprocess( + LooseDataInstantiateContext *instantiate_context) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + LinkNode *itemlink; + + /* First pass on obdata to enable their instantiation by default, then do a second pass on + * objects to clear it for any obdata already in use. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + + id->tag |= LIB_TAG_DOIT; + } + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + Object *ob = (Object *)id; + Object *new_ob = (Object *)id->newid; + if (ob->data != NULL) { + ((ID *)(ob->data))->tag &= ~LIB_TAG_DOIT; + } + if (new_ob != NULL && new_ob->data != NULL) { + ((ID *)(new_ob->data))->tag &= ~LIB_TAG_DOIT; + } + } +} + +/* Test whether some ancestor collection is also tagged for instantiation (return true) or not + * (return false). */ +static bool loose_data_instantiate_collection_parents_check_recursive(Collection *collection) +{ + for (CollectionParent *parent_collection = collection->parents.first; parent_collection != NULL; + parent_collection = parent_collection->next) { + if ((parent_collection->collection->id.tag & LIB_TAG_DOIT) != 0) { + return true; + } + if (loose_data_instantiate_collection_parents_check_recursive(parent_collection->collection)) { + return true; + } + } + return false; +} + +static void loose_data_instantiate_collection_process( + LooseDataInstantiateContext *instantiate_context) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + Main *bmain = lapp_context->params->bmain; + Scene *scene = lapp_context->params->context.scene; + ViewLayer *view_layer = lapp_context->params->context.view_layer; + const View3D *v3d = lapp_context->params->context.v3d; + + const bool do_append = (lapp_context->params->flag & FILE_LINK) == 0; + const bool do_instantiate_as_empty = (lapp_context->params->flag & + BLO_LIBLINK_COLLECTION_INSTANCE) != 0; + + /* NOTE: For collections we only view_layer-instantiate duplicated collections that have + * non-instantiated objects in them. + * NOTE: Also avoid view-layer-instantiating of collections children of other instantiated + * collections. This is why we need two passes here. */ + LinkNode *itemlink; + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL || GS(id->name) != ID_GR) { + continue; + } + + /* Forced instantiation of indirectly appended collections is not wanted. Users can now + * easily instantiate collections (and their objects) as needed by themselves. See T67032. */ + /* We need to check that objects in that collections are already instantiated in a scene. + * Otherwise, it's better to add the collection to the scene's active collection, than to + * instantiate its objects in active scene's collection directly. See T61141. + * + * NOTE: We only check object directly into that collection, not recursively into its + * children. + */ + Collection *collection = (Collection *)id; + /* The collection could be linked/appended together with an Empty object instantiating it, + * better not instantiate the collection in the view-layer in that case. + * + * Can easily happen when copy/pasting such instantiating empty, see T93839. */ + const bool collection_is_instantiated = collection_instantiated_by_any_object(bmain, + collection); + /* Always consider adding collections directly selected by the user. */ + bool do_add_collection = (item->tag & LINK_APPEND_TAG_INDIRECT) == 0 && + !collection_is_instantiated; + /* In linking case, do not enforce instantiating non-directly linked collections/objects. + * This avoids cluttering the view-layers, user can instantiate themselves specific collections + * or objects easily from the Outliner if needed. */ + if (!do_add_collection && do_append && !collection_is_instantiated) { + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + if (!object_in_any_scene(bmain, ob)) { + do_add_collection = true; + break; + } + } + } + if (do_add_collection) { + collection->id.tag |= LIB_TAG_DOIT; + } + } + + /* Second loop to actually instantiate collections tagged as such in first loop, unless some of + * their ancestor is also instantiated in case this is not an empty-instantiation. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL || GS(id->name) != ID_GR) { + continue; + } + + Collection *collection = (Collection *)id; + bool do_add_collection = (id->tag & LIB_TAG_DOIT) != 0; + + if (!do_add_collection) { + continue; + } + /* When instantiated into view-layer, do not add collections if one of their parents is also + * instantiated. */ + if (!do_instantiate_as_empty && + loose_data_instantiate_collection_parents_check_recursive(collection)) { + continue; + } + /* When instantiated as empty, do not add indirectly linked (i.e. non-user-selected) + * collections. */ + if (do_instantiate_as_empty && (item->tag & LINK_APPEND_TAG_INDIRECT) != 0) { + continue; + } + + loose_data_instantiate_ensure_active_collection(instantiate_context); + Collection *active_collection = instantiate_context->active_collection; + + if (do_instantiate_as_empty) { + /* BKE_object_add(...) messes with the selection. */ + Object *ob = BKE_object_add_only_object(bmain, OB_EMPTY, collection->id.name + 2); + ob->type = OB_EMPTY; + ob->empty_drawsize = U.collection_instance_empty_size; + + const bool set_selected = (lapp_context->params->flag & FILE_AUTOSELECT) != 0; + /* TODO: why is it OK to make this active here but not in other situations? + * See other callers of #object_base_instance_init */ + const bool set_active = set_selected; + loose_data_instantiate_object_base_instance_init( + bmain, active_collection, ob, view_layer, v3d, lapp_context->params->flag, set_active); + + /* Assign the collection. */ + ob->instance_collection = collection; + id_us_plus(&collection->id); + ob->transflag |= OB_DUPLICOLLECTION; + copy_v3_v3(ob->loc, scene->cursor.location); + } + else { + /* Add collection as child of active collection. */ + BKE_collection_child_add(bmain, active_collection, collection); + + if ((lapp_context->params->flag & FILE_AUTOSELECT) != 0) { + LISTBASE_FOREACH (CollectionObject *, coll_ob, &collection->gobject) { + Object *ob = coll_ob->ob; + Base *base = BKE_view_layer_base_find(view_layer, ob); + if (base) { + base->flag |= BASE_SELECTED; + BKE_scene_object_base_flag_sync_from_base(base); + } + } + } + } + } +} + +static void loose_data_instantiate_object_process(LooseDataInstantiateContext *instantiate_context) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + Main *bmain = lapp_context->params->bmain; + ViewLayer *view_layer = lapp_context->params->context.view_layer; + const View3D *v3d = lapp_context->params->context.v3d; + + /* Do NOT make base active here! screws up GUI stuff, + * if you want it do it at the editor level. */ + const bool object_set_active = false; + + const bool is_linking = (lapp_context->params->flag & FILE_LINK) != 0; + + /* NOTE: For objects we only view_layer-instantiate duplicated objects that are not yet used + * anywhere. */ + LinkNode *itemlink; + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + + /* In linking case, never instantiate stray objects that are not directly linked. + * + * While this is not ideal (in theory no object should remain un-owned), in case of indirectly + * linked objects, the other solution would be to add them to a local collection, which would + * make them directly linked. Think for now keeping them indirectly linked is more important. + * Ref. T93757. + */ + if (is_linking && (item->tag & LINK_APPEND_TAG_INDIRECT) != 0) { + continue; + } + + Object *ob = (Object *)id; + + if (object_in_any_collection(bmain, ob)) { + continue; + } + + loose_data_instantiate_ensure_active_collection(instantiate_context); + Collection *active_collection = instantiate_context->active_collection; + + CLAMP_MIN(ob->id.us, 0); + ob->mode = OB_MODE_OBJECT; + + loose_data_instantiate_object_base_instance_init(bmain, + active_collection, + ob, + view_layer, + v3d, + lapp_context->params->flag, + object_set_active); + } +} + +static void loose_data_instantiate_obdata_process(LooseDataInstantiateContext *instantiate_context) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + Main *bmain = lapp_context->params->bmain; + Scene *scene = lapp_context->params->context.scene; + ViewLayer *view_layer = lapp_context->params->context.view_layer; + const View3D *v3d = lapp_context->params->context.v3d; + + /* Do NOT make base active here! screws up GUI stuff, + * if you want it do it at the editor level. */ + const bool object_set_active = false; + + LinkNode *itemlink; + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL) { + continue; + } + const ID_Type idcode = GS(id->name); + if (!OB_DATA_SUPPORT_ID(idcode)) { + continue; + } + if ((id->tag & LIB_TAG_DOIT) == 0) { + continue; + } + + loose_data_instantiate_ensure_active_collection(instantiate_context); + Collection *active_collection = instantiate_context->active_collection; + + const int type = BKE_object_obdata_to_type(id); + BLI_assert(type != -1); + Object *ob = BKE_object_add_only_object(bmain, type, id->name + 2); + ob->data = id; + id_us_plus(id); + BKE_object_materials_test(bmain, ob, ob->data); + + loose_data_instantiate_object_base_instance_init(bmain, + active_collection, + ob, + view_layer, + v3d, + lapp_context->params->flag, + object_set_active); + + copy_v3_v3(ob->loc, scene->cursor.location); + + id->tag &= ~LIB_TAG_DOIT; + } +} + +static void loose_data_instantiate_object_rigidbody_postprocess( + LooseDataInstantiateContext *instantiate_context) +{ + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + Main *bmain = lapp_context->params->bmain; + + LinkNode *itemlink; + /* Add rigid body objects and constraints to current RB world(s). */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = loose_data_instantiate_process_check(instantiate_context, item); + if (id == NULL || GS(id->name) != ID_OB) { + continue; + } + BKE_rigidbody_ensure_local_object(bmain, (Object *)id); + } +} + +static void loose_data_instantiate(LooseDataInstantiateContext *instantiate_context) +{ + if (instantiate_context->lapp_context->params->context.scene == NULL) { + /* In some cases, like the asset drag&drop e.g., the caller code manages instantiation itself. + */ + return; + } + + BlendfileLinkAppendContext *lapp_context = instantiate_context->lapp_context; + const bool do_obdata = (lapp_context->params->flag & BLO_LIBLINK_OBDATA_INSTANCE) != 0; + + /* First pass on obdata to enable their instantiation by default, then do a second pass on + * objects to clear it for any obdata already in use. */ + if (do_obdata) { + loose_data_instantiate_obdata_preprocess(instantiate_context); + } + + /* First do collections, then objects, then obdata. */ + loose_data_instantiate_collection_process(instantiate_context); + loose_data_instantiate_object_process(instantiate_context); + if (do_obdata) { + loose_data_instantiate_obdata_process(instantiate_context); + } + + loose_data_instantiate_object_rigidbody_postprocess(instantiate_context); +} + +static void new_id_to_item_mapping_add(BlendfileLinkAppendContext *lapp_context, + ID *id, + BlendfileLinkAppendContextItem *item) +{ + BLI_ghash_insert(lapp_context->new_id_to_item, id, item); + + /* This ensures that if a liboverride reference is also linked/used by some other appended + * data, it gets a local copy instead of being made directly local, so that the liboverride + * references remain valid (i.e. linked data). */ + if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + id->override_library->reference->tag |= LIB_TAG_PRE_EXISTING; + } +} + +/* Generate a mapping between newly linked IDs and their items, and tag linked IDs used as + * liboverride references as already existing. */ +static void new_id_to_item_mapping_create(BlendfileLinkAppendContext *lapp_context) +{ + lapp_context->new_id_to_item = BLI_ghash_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + for (LinkNode *itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + + new_id_to_item_mapping_add(lapp_context, id, item); + } +} + +static int foreach_libblock_link_append_callback(LibraryIDLinkCallbackData *cb_data) +{ + /* NOTE: It is important to also skip liboverride references here, as those should never be made + * local. */ + if (cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_INTERNAL | IDWALK_CB_LOOPBACK | + IDWALK_CB_OVERRIDE_LIBRARY_REFERENCE)) { + return IDWALK_RET_NOP; + } + + BlendfileLinkAppendContextCallBack *data = cb_data->user_data; + ID *id = *cb_data->id_pointer; + + if (id == NULL) { + return IDWALK_RET_NOP; + } + + if (!BKE_idtype_idcode_is_linkable(GS(id->name))) { + /* While we do not want to add non-linkable ID (shape keys...) to the list of linked items, + * unfortunately they can use fully linkable valid IDs too, like actions. Those need to be + * processed, so we need to recursively deal with them here. */ + /* NOTE: Since we are by-passing checks in `BKE_library_foreach_ID_link` by manually calling it + * recursively, we need to take care of potential recursion cases ourselves (e.g.animdata of + * shape-key referencing the shape-key itself). */ + if (id != cb_data->id_self) { + BKE_library_foreach_ID_link( + cb_data->bmain, id, foreach_libblock_link_append_callback, data, IDWALK_NOP); + } + return IDWALK_RET_NOP; + } + + /* In linking case, we always consider all linked IDs, even indirectly ones, for instantiation, + * so we need to add them all to the items list. + * + * In appending case, when `do_recursive` is false, we only make local IDs from same + * library(-ies) as the initially directly linked ones. + * + * NOTE: Since in append case, linked IDs are also fully skipped during instantiation step (see + * #append_loose_data_instantiate_process_check), we can avoid adding them to the items list + * completely. */ + const bool do_link = (data->lapp_context->params->flag & FILE_LINK) != 0; + const bool do_recursive = (data->lapp_context->params->flag & BLO_LIBLINK_APPEND_RECURSIVE) != + 0 || + do_link; + if (!do_recursive && cb_data->id_owner->lib != id->lib) { + return IDWALK_RET_NOP; + } + + BlendfileLinkAppendContextItem *item = BLI_ghash_lookup(data->lapp_context->new_id_to_item, id); + if (item == NULL) { + item = BKE_blendfile_link_append_context_item_add( + data->lapp_context, id->name, GS(id->name), NULL); + item->new_id = id; + item->source_library = id->lib; + /* Since we did not have an item for that ID yet, we know user did not selected it explicitly, + * it was rather linked indirectly. This info is important for instantiation of collections. */ + item->tag |= LINK_APPEND_TAG_INDIRECT; + /* In linking case we already know what we want to do with those items. */ + if (do_link) { + item->action = LINK_APPEND_ACT_KEEP_LINKED; + } + new_id_to_item_mapping_add(data->lapp_context, id, item); + } + + /* NOTE: currently there is no need to do anything else here, but in the future this would be + * the place to add specific per-usage decisions on how to append an ID. */ + + return IDWALK_RET_NOP; +} + +/** \} */ + +/** \name Library link/append code. + * \{ */ + +void BKE_blendfile_append(BlendfileLinkAppendContext *lapp_context, ReportList *reports) +{ + if (lapp_context->num_items == 0) { + /* Nothing to append. */ + return; + } + + Main *bmain = lapp_context->params->bmain; + + BLI_assert((lapp_context->params->flag & FILE_LINK) == 0); + + const bool set_fakeuser = (lapp_context->params->flag & BLO_LIBLINK_APPEND_SET_FAKEUSER) != 0; + const bool do_reuse_local_id = (lapp_context->params->flag & + BLO_LIBLINK_APPEND_LOCAL_ID_REUSE) != 0; + + const int make_local_common_flags = LIB_ID_MAKELOCAL_FULL_LIBRARY | + ((lapp_context->params->flag & + BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR) != 0 ? + LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR : + 0); + + LinkNode *itemlink; + + new_id_to_item_mapping_create(lapp_context); + lapp_context->library_weak_reference_mapping = BKE_main_library_weak_reference_create(bmain); + + /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect + * dependencies), this list will grow and we will process those IDs later, leading to a flatten + * recursive processing of all the linked dependencies. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(item->userdata == NULL); + + ID *existing_local_id = BKE_idtype_idcode_append_is_reusable(GS(id->name)) ? + BKE_main_library_weak_reference_search_item( + lapp_context->library_weak_reference_mapping, + id->lib->filepath, + id->name) : + NULL; + + if (item->action != LINK_APPEND_ACT_UNSET) { + /* Already set, pass. */ + } + if (GS(id->name) == ID_OB && ((Object *)id)->proxy_from != NULL) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' is proxified, keeping it linked...", id->name); + item->action = LINK_APPEND_ACT_KEEP_LINKED; + } + else if (do_reuse_local_id && existing_local_id != NULL) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' as a matching local one, re-using it...", id->name); + item->action = LINK_APPEND_ACT_REUSE_LOCAL; + item->userdata = existing_local_id; + } + else if (id->tag & LIB_TAG_PRE_EXISTING) { + CLOG_INFO(&LOG, 3, "Appended ID '%s' was already linked, need to copy it...", id->name); + item->action = LINK_APPEND_ACT_COPY_LOCAL; + } + else { + CLOG_INFO(&LOG, 3, "Appended ID '%s' will be made local...", id->name); + item->action = LINK_APPEND_ACT_MAKE_LOCAL; + } + + /* Only check dependencies if we are not keeping linked data, nor re-using existing local data. + */ + if (!ELEM(item->action, LINK_APPEND_ACT_KEEP_LINKED, LINK_APPEND_ACT_REUSE_LOCAL)) { + BlendfileLinkAppendContextCallBack cb_data = { + .lapp_context = lapp_context, .item = item, .reports = reports}; + BKE_library_foreach_ID_link( + bmain, id, foreach_libblock_link_append_callback, &cb_data, IDWALK_NOP); + } + + /* If we found a matching existing local id but are not re-using it, we need to properly clear + * its weak reference to linked data. */ + if (existing_local_id != NULL && + !ELEM(item->action, LINK_APPEND_ACT_KEEP_LINKED, LINK_APPEND_ACT_REUSE_LOCAL)) { + BKE_main_library_weak_reference_remove_item(lapp_context->library_weak_reference_mapping, + id->lib->filepath, + id->name, + existing_local_id); + } + } + + /* Effectively perform required operation on every linked ID. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + + ID *local_appended_new_id = NULL; + char lib_filepath[FILE_MAX]; + BLI_strncpy(lib_filepath, id->lib->filepath, sizeof(lib_filepath)); + char lib_id_name[MAX_ID_NAME]; + BLI_strncpy(lib_id_name, id->name, sizeof(lib_id_name)); + + switch (item->action) { + case LINK_APPEND_ACT_COPY_LOCAL: + BKE_lib_id_make_local(bmain, id, make_local_common_flags | LIB_ID_MAKELOCAL_FORCE_COPY); + local_appended_new_id = id->newid; + break; + case LINK_APPEND_ACT_MAKE_LOCAL: + BKE_lib_id_make_local(bmain, + id, + make_local_common_flags | LIB_ID_MAKELOCAL_FORCE_LOCAL | + LIB_ID_MAKELOCAL_OBJECT_NO_PROXY_CLEARING); + BLI_assert(id->newid == NULL); + local_appended_new_id = id; + break; + case LINK_APPEND_ACT_KEEP_LINKED: + /* Nothing to do here. */ + break; + case LINK_APPEND_ACT_REUSE_LOCAL: + /* We only need to set `newid` to ID found in previous loop, for proper remapping. */ + ID_NEW_SET(id, item->userdata); + /* This is not a 'new' local appended id, do not set `local_appended_new_id` here. */ + break; + case LINK_APPEND_ACT_UNSET: + CLOG_ERROR( + &LOG, "Unexpected unset append action for '%s' ID, assuming 'keep link'", id->name); + break; + default: + BLI_assert(0); + } + + if (local_appended_new_id != NULL) { + if (BKE_idtype_idcode_append_is_reusable(GS(local_appended_new_id->name))) { + BKE_main_library_weak_reference_add_item(lapp_context->library_weak_reference_mapping, + lib_filepath, + lib_id_name, + local_appended_new_id); + } + + if (set_fakeuser) { + if (!ELEM(GS(local_appended_new_id->name), ID_OB, ID_GR)) { + /* Do not set fake user on objects nor collections (instancing). */ + id_fake_user_set(local_appended_new_id); + } + } + } + } + + BKE_main_library_weak_reference_destroy(lapp_context->library_weak_reference_mapping); + lapp_context->library_weak_reference_mapping = NULL; + + /* Remap IDs as needed. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + + if (item->action == LINK_APPEND_ACT_KEEP_LINKED) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + if (ELEM(item->action, LINK_APPEND_ACT_COPY_LOCAL, LINK_APPEND_ACT_REUSE_LOCAL)) { + BLI_assert(ID_IS_LINKED(id)); + id = id->newid; + if (id == NULL) { + continue; + } + } + + BLI_assert(!ID_IS_LINKED(id)); + + BKE_libblock_relink_to_newid(bmain, id, 0); + } + + /* Remove linked IDs when a local existing data has been reused instead. */ + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + + if (item->action != LINK_APPEND_ACT_REUSE_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + BLI_assert(id->newid != NULL); + + id->tag |= LIB_TAG_DOIT; + item->new_id = id->newid; + } + BKE_id_multi_tagged_delete(bmain); + + /* Instantiate newly created (duplicated) IDs as needed. */ + LooseDataInstantiateContext instantiate_context = {.lapp_context = lapp_context, + .active_collection = NULL}; + loose_data_instantiate(&instantiate_context); + + /* Attempt to deal with object proxies. + * + * NOTE: Copied from `BKE_library_make_local`, but this is not really working (as in, not + * producing any useful result in any known use case), neither here nor in + * `BKE_library_make_local` currently. + * Proxies are end of life anyway, so not worth spending time on this. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + + if (item->action != LINK_APPEND_ACT_COPY_LOCAL) { + continue; + } + + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(ID_IS_LINKED(id)); + + /* Attempt to re-link copied proxy objects. This allows appending of an entire scene + * from another blend file into this one, even when that blend file contains proxified + * armatures that have local references. Since the proxified object needs to be linked + * (not local), this will only work when the "Localize all" checkbox is disabled. + * TL;DR: this is a dirty hack on top of an already weak feature (proxies). */ + if (GS(id->name) == ID_OB && ((Object *)id)->proxy != NULL) { + Object *ob = (Object *)id; + Object *ob_new = (Object *)id->newid; + bool is_local = false, is_lib = false; + + /* Proxies only work when the proxified object is linked-in from a library. */ + if (!ID_IS_LINKED(ob->proxy)) { + CLOG_WARN(&LOG, + "Proxy object %s will lose its link to %s, because the " + "proxified object is local", + id->newid->name, + ob->proxy->id.name); + continue; + } + + BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib); + + /* We can only switch the proxy'ing to a made-local proxy if it is no longer + * referred to from a library. Not checking for local use; if new local proxy + * was not used locally would be a nasty bug! */ + if (is_local || is_lib) { + CLOG_WARN(&LOG, + "Made-local proxy object %s will lose its link to %s, " + "because the linked-in proxy is referenced (is_local=%i, is_lib=%i)", + id->newid->name, + ob->proxy->id.name, + is_local, + is_lib); + } + else { + /* we can switch the proxy'ing from the linked-in to the made-local proxy. + * BKE_object_make_proxy() shouldn't be used here, as it allocates memory that + * was already allocated by object_make_local() (which called BKE_object_copy). */ + ob_new->proxy = ob->proxy; + ob_new->proxy_group = ob->proxy_group; + ob_new->proxy_from = ob->proxy_from; + ob_new->proxy->proxy_from = ob_new; + ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + } + } + } + + BKE_main_id_newptr_and_tag_clear(bmain); +} + +void BKE_blendfile_link(BlendfileLinkAppendContext *lapp_context, ReportList *reports) +{ + if (lapp_context->num_items == 0) { + /* Nothing to be linked. */ + return; + } + + BLI_assert(lapp_context->num_libraries != 0); + + Main *mainl; + Library *lib; + + LinkNode *liblink, *itemlink; + int lib_idx, item_idx; + + for (lib_idx = 0, liblink = lapp_context->libraries.list; liblink; + lib_idx++, liblink = liblink->next) { + BlendfileLinkAppendContextLibrary *lib_context = liblink->link; + char *libname = lib_context->path; + BlendHandle *blo_handle = link_append_context_library_blohandle_ensure( + lapp_context, lib_context, reports); + + if (blo_handle == NULL) { + /* Unlikely since we just browsed it, but possible + * Error reports will have been made by BLO_blendhandle_from_file() */ + continue; + } + + /* here appending/linking starts */ + + mainl = BLO_library_link_begin(&blo_handle, libname, lapp_context->params); + lib = mainl->curlib; + BLI_assert(lib); + UNUSED_VARS_NDEBUG(lib); + + if (mainl->versionfile < 250) { + BKE_reportf(reports, + RPT_WARNING, + "Linking or appending from a very old .blend file format (%d.%d), no animation " + "conversion will " + "be done! You may want to re-save your lib file with current Blender", + mainl->versionfile, + mainl->subversionfile); + } + + /* For each lib file, we try to link all items belonging to that lib, + * and tag those successful to not try to load them again with the other libs. */ + for (item_idx = 0, itemlink = lapp_context->items.list; itemlink; + item_idx++, itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *new_id; + + if (!BLI_BITMAP_TEST(item->libraries, lib_idx)) { + continue; + } + + new_id = BLO_library_link_named_part( + mainl, &blo_handle, item->idcode, item->name, lapp_context->params); + + if (new_id) { + /* If the link is successful, clear item's libs 'todo' flags. + * This avoids trying to link same item with other libraries to come. */ + BLI_bitmap_set_all(item->libraries, false, lapp_context->num_libraries); + item->new_id = new_id; + item->source_library = new_id->lib; + } + } + + BLO_library_link_end(mainl, &blo_handle, lapp_context->params); + link_append_context_library_blohandle_release(lapp_context, lib_context); + } + + /* Instantiate newly linked IDs as needed, if no append is scheduled. */ + if ((lapp_context->params->flag & FILE_LINK) != 0 && + lapp_context->params->context.scene != NULL) { + new_id_to_item_mapping_create(lapp_context); + /* NOTE: Since we append items for IDs not already listed (i.e. implicitly linked indirect + * dependencies), this list will grow and we will process those IDs later, leading to a flatten + * recursive processing of all the linked dependencies. */ + for (itemlink = lapp_context->items.list; itemlink; itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *id = item->new_id; + if (id == NULL) { + continue; + } + BLI_assert(item->userdata == NULL); + + BlendfileLinkAppendContextCallBack cb_data = { + .lapp_context = lapp_context, .item = item, .reports = reports}; + BKE_library_foreach_ID_link(lapp_context->params->bmain, + id, + foreach_libblock_link_append_callback, + &cb_data, + IDWALK_NOP); + } + + LooseDataInstantiateContext instantiate_context = {.lapp_context = lapp_context, + .active_collection = NULL}; + loose_data_instantiate(&instantiate_context); + } +} + +/** \} */ + +/** \name Library relocating code. + * \{ */ + +static void blendfile_library_relocate_remap(Main *bmain, + ID *old_id, + ID *new_id, + ReportList *reports, + const bool do_reload, + const short remap_flags) +{ + BLI_assert(old_id); + if (do_reload) { + /* Since we asked for placeholders in case of missing IDs, + * we expect to always get a valid one. */ + BLI_assert(new_id); + } + if (new_id) { + CLOG_INFO(&LOG, + 4, + "Before remap of %s, old_id users: %d, new_id users: %d", + old_id->name, + old_id->us, + new_id->us); + BKE_libblock_remap_locked(bmain, old_id, new_id, remap_flags); + + if (old_id->flag & LIB_FAKEUSER) { + id_fake_user_clear(old_id); + id_fake_user_set(new_id); + } + + CLOG_INFO(&LOG, + 4, + "After remap of %s, old_id users: %d, new_id users: %d", + old_id->name, + old_id->us, + new_id->us); + + /* In some cases, new_id might become direct link, remove parent of library in this case. */ + if (new_id->lib->parent && (new_id->tag & LIB_TAG_INDIRECT) == 0) { + if (do_reload) { + BLI_assert_unreachable(); /* Should not happen in 'pure' reload case... */ + } + new_id->lib->parent = NULL; + } + } + + if (old_id->us > 0 && new_id && old_id->lib == new_id->lib) { + /* Note that this *should* not happen - but better be safe than sorry in this area, + * at least until we are 100% sure this cannot ever happen. + * Also, we can safely assume names were unique so far, + * so just replacing '.' by '~' should work, + * but this does not totally rules out the possibility of name collision. */ + size_t len = strlen(old_id->name); + size_t dot_pos; + bool has_num = false; + + for (dot_pos = len; dot_pos--;) { + char c = old_id->name[dot_pos]; + if (c == '.') { + break; + } + if (c < '0' || c > '9') { + has_num = false; + break; + } + has_num = true; + } + + if (has_num) { + old_id->name[dot_pos] = '~'; + } + else { + len = MIN2(len, MAX_ID_NAME - 7); + BLI_strncpy(&old_id->name[len], "~000", 7); + } + + id_sort_by_name(which_libbase(bmain, GS(old_id->name)), old_id, NULL); + + BKE_reportf( + reports, + RPT_WARNING, + "Lib Reload: Replacing all references to old data-block '%s' by reloaded one failed, " + "old one (%d remaining users) had to be kept and was renamed to '%s'", + new_id->name, + old_id->us, + old_id->name); + } +} + +void BKE_blendfile_library_relocate(BlendfileLinkAppendContext *lapp_context, + ReportList *reports, + Library *library, + const bool do_reload) +{ + ListBase *lbarray[INDEX_ID_MAX]; + int lba_idx; + + LinkNode *itemlink; + int item_idx; + + Main *bmain = lapp_context->params->bmain; + + /* All override rules need to be up to date, since there will be no do_version here, otherwise + * older, now-invalid rules might be applied and likely fail, or some changes might be missing, + * etc. See T93353. */ + BKE_lib_override_library_main_operations_create(bmain, true); + + /* Remove all IDs to be reloaded from Main. */ + lba_idx = set_listbasepointers(bmain, lbarray); + while (lba_idx--) { + ID *id = lbarray[lba_idx]->first; + const short idcode = id ? GS(id->name) : 0; + + if (!id || !BKE_idtype_idcode_is_linkable(idcode)) { + /* No need to reload non-linkable datatypes, + * those will get relinked with their 'users ID'. */ + continue; + } + + for (; id; id = id->next) { + if (id->lib == library) { + BlendfileLinkAppendContextItem *item; + + /* We remove it from current Main, and add it to items to link... */ + /* Note that non-linkable IDs (like e.g. shapekeys) are also explicitly linked here... */ + BLI_remlink(lbarray[lba_idx], id); + /* Usual special code for ShapeKeys snowflakes... */ + Key *old_key = BKE_key_from_id(id); + if (old_key != NULL) { + BLI_remlink(which_libbase(bmain, GS(old_key->id.name)), &old_key->id); + } + + item = BKE_blendfile_link_append_context_item_add(lapp_context, id->name + 2, idcode, id); + BLI_bitmap_set_all(item->libraries, true, (size_t)lapp_context->num_libraries); + + CLOG_INFO(&LOG, 4, "Datablock to seek for: %s", id->name); + } + } + } + + if (lapp_context->num_items == 0) { + /* Early out in case there is nothing to do. */ + return; + } + + BKE_main_id_tag_all(bmain, LIB_TAG_PRE_EXISTING, true); + + /* We do not want any instantiation here! */ + BKE_blendfile_link(lapp_context, reports); + + BKE_main_lock(bmain); + + /* We add back old id to bmain. + * We need to do this in a first, separated loop, otherwise some of those may not be handled by + * ID remapping, which means they would still reference old data to be deleted... */ + for (item_idx = 0, itemlink = lapp_context->items.list; itemlink; + item_idx++, itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *old_id = item->userdata; + + BLI_assert(old_id); + BLI_addtail(which_libbase(bmain, GS(old_id->name)), old_id); + + /* Usual special code for ShapeKeys snowflakes... */ + Key *old_key = BKE_key_from_id(old_id); + if (old_key != NULL) { + BLI_addtail(which_libbase(bmain, GS(old_key->id.name)), &old_key->id); + } + } + + /* Since our (old) reloaded IDs were removed from main, the user count done for them in linking + * code is wrong, we need to redo it here after adding them back to main. */ + BKE_main_id_refcount_recompute(bmain, false); + + /* Note that in reload case, we also want to replace indirect usages. */ + const short remap_flags = ID_REMAP_SKIP_NEVER_NULL_USAGE | + ID_REMAP_NO_INDIRECT_PROXY_DATA_USAGE | + (do_reload ? 0 : ID_REMAP_SKIP_INDIRECT_USAGE); + for (item_idx = 0, itemlink = lapp_context->items.list; itemlink; + item_idx++, itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *old_id = item->userdata; + ID *new_id = item->new_id; + + blendfile_library_relocate_remap(bmain, old_id, new_id, reports, do_reload, remap_flags); + if (new_id == NULL) { + continue; + } + /* Usual special code for ShapeKeys snowflakes... */ + Key **old_key_p = BKE_key_from_id_p(old_id); + if (old_key_p == NULL) { + continue; + } + Key *old_key = *old_key_p; + Key *new_key = BKE_key_from_id(new_id); + if (old_key != NULL) { + *old_key_p = NULL; + id_us_min(&old_key->id); + blendfile_library_relocate_remap( + bmain, &old_key->id, &new_key->id, reports, do_reload, remap_flags); + *old_key_p = old_key; + id_us_plus_no_lib(&old_key->id); + } + } + + BKE_main_unlock(bmain); + + for (item_idx = 0, itemlink = lapp_context->items.list; itemlink; + item_idx++, itemlink = itemlink->next) { + BlendfileLinkAppendContextItem *item = itemlink->link; + ID *old_id = item->userdata; + + if (old_id->us == 0) { + BKE_id_free(bmain, old_id); + } + } + + /* Some datablocks can get reloaded/replaced 'silently' because they are not linkable + * (shape keys e.g.), so we need another loop here to clear old ones if possible. */ + lba_idx = set_listbasepointers(bmain, lbarray); + while (lba_idx--) { + ID *id, *id_next; + for (id = lbarray[lba_idx]->first; id; id = id_next) { + id_next = id->next; + /* XXX That check may be a bit to generic/permissive? */ + if (id->lib && (id->flag & LIB_TAG_PRE_EXISTING) && id->us == 0) { + BKE_id_free(bmain, id); + } + } + } + + /* Get rid of no more used libraries... */ + BKE_main_id_tag_idcode(bmain, ID_LI, LIB_TAG_DOIT, true); + lba_idx = set_listbasepointers(bmain, lbarray); + while (lba_idx--) { + ID *id; + for (id = lbarray[lba_idx]->first; id; id = id->next) { + if (id->lib) { + id->lib->id.tag &= ~LIB_TAG_DOIT; + } + } + } + Library *lib, *lib_next; + for (lib = which_libbase(bmain, ID_LI)->first; lib; lib = lib_next) { + lib_next = lib->id.next; + if (lib->id.tag & LIB_TAG_DOIT) { + id_us_clear_real(&lib->id); + if (lib->id.us == 0) { + BKE_id_free(bmain, (ID *)lib); + } + } + } + + /* Update overrides of reloaded linked data-blocks. */ + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id) || + (id->tag & LIB_TAG_PRE_EXISTING) == 0) { + continue; + } + if ((id->override_library->reference->tag & LIB_TAG_PRE_EXISTING) == 0) { + BKE_lib_override_library_update(bmain, id); + } + } + FOREACH_MAIN_ID_END; + + /* Resync overrides if needed. */ + if (!USER_EXPERIMENTAL_TEST(&U, no_override_auto_resync)) { + BKE_lib_override_library_main_resync(bmain, + lapp_context->params->context.scene, + lapp_context->params->context.view_layer, + &(struct BlendFileReadReport){ + .reports = reports, + }); + /* We need to rebuild some of the deleted override rules (for UI feedback purpose). */ + BKE_lib_override_library_main_operations_create(bmain, true); + } + + BKE_main_collection_sync(bmain); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/boids.c b/source/blender/blenkernel/intern/boids.c index a7257133821..a9f26d00007 100644 --- a/source/blender/blenkernel/intern/boids.c +++ b/source/blender/blenkernel/intern/boids.c @@ -1061,7 +1061,6 @@ static int boid_condition_is_true(BoidCondition *cond) } #endif -/* determines the velocity the boid wants to have */ void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa) { BoidRule *rule; @@ -1218,7 +1217,6 @@ void boid_brain(BoidBrainData *bbd, int p, ParticleData *pa) } } } -/* tries to realize the wanted velocity taking all constraints into account */ void boid_body(BoidBrainData *bbd, ParticleData *pa) { BoidSettings *boids = bbd->part->boids; diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c index 371ec14876b..a1570b4e031 100644 --- a/source/blender/blenkernel/intern/bpath.c +++ b/source/blender/blenkernel/intern/bpath.c @@ -15,7 +15,7 @@ */ /** \file - * \ingroup bli + * \ingroup bke */ /* TODO: @@ -66,13 +66,14 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" -#include "BKE_font.h" +#include "BKE_idtype.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 */ @@ -87,213 +88,157 @@ static CLG_LogRef LOG = {"bke.bpath"}; /* -------------------------------------------------------------------- */ -/** \name Check Missing Files +/** \name Generic File Path Traversal API * \{ */ -static bool checkMissingFiles_visit_cb(void *userdata, - char *UNUSED(path_dst), - const char *path_src) +void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, ID *id) { - ReportList *reports = (ReportList *)userdata; + const eBPathForeachFlag flag = bpath_data->flag; + const char *absbase = (flag & BKE_BPATH_FOREACH_PATH_ABSOLUTE) ? + ID_BLEND_PATH(bpath_data->bmain, id) : + NULL; + bpath_data->absolute_base_path = absbase; - if (!BLI_exists(path_src)) { - BKE_reportf(reports, RPT_WARNING, "Path '%s' not found", path_src); + if ((flag & BKE_BPATH_FOREACH_PATH_SKIP_LINKED) && ID_IS_LINKED(id)) { + return; } - return false; -} - -/* high level function */ -void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports) -{ - BKE_bpath_traverse_main(bmain, - checkMissingFiles_visit_cb, - BKE_BPATH_TRAVERSE_ABS | BKE_BPATH_TRAVERSE_SKIP_PACKED, - reports); -} + if (id->library_weak_reference != NULL && + (flag & BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES) == 0) { + BKE_bpath_foreach_path_fixed_process(bpath_data, id->library_weak_reference->library_filepath); + } -/** \} */ + bNodeTree *embedded_node_tree = ntreeFromID(id); + if (embedded_node_tree != NULL) { + BKE_bpath_foreach_path_id(bpath_data, &embedded_node_tree->id); + } -/* -------------------------------------------------------------------- */ -/** \name Rebase Relative Paths - * \{ */ + const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id); -typedef struct BPathRebase_Data { - const char *basedir_src; - const char *basedir_dst; - ReportList *reports; + BLI_assert(id_type != NULL); + if (id_type == NULL || id_type->foreach_path == NULL) { + return; + } - int count_tot; - int count_changed; - int count_failed; -} BPathRebase_Data; + id_type->foreach_path(id, bpath_data); +} -static bool bpath_relative_rebase_visit_cb(void *userdata, char *path_dst, const char *path_src) +void BKE_bpath_foreach_path_main(BPathForeachPathData *bpath_data) { - BPathRebase_Data *data = (BPathRebase_Data *)userdata; + ID *id; + FOREACH_MAIN_ID_BEGIN (bpath_data->bmain, id) { + BKE_bpath_foreach_path_id(bpath_data, id); + } + FOREACH_MAIN_ID_END; +} - data->count_tot++; +bool BKE_bpath_foreach_path_fixed_process(BPathForeachPathData *bpath_data, char *path) +{ + const char *absolute_base_path = bpath_data->absolute_base_path; - if (BLI_path_is_rel(path_src)) { - char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE]; - BLI_strncpy(filepath, path_src, FILE_MAX); - if (BLI_path_abs(filepath, data->basedir_src)) { - BLI_path_normalize(NULL, filepath); + char path_src_buf[FILE_MAX]; + const char *path_src; + char path_dst[FILE_MAX]; - /* This may fail, if so it's fine to leave absolute since the path is still valid. */ - BLI_path_rel(filepath, data->basedir_dst); + if (absolute_base_path) { + BLI_strncpy(path_src_buf, path, sizeof(path_src_buf)); + BLI_path_abs(path_src_buf, absolute_base_path); + path_src = path_src_buf; + } + else { + path_src = path; + } - BLI_strncpy(path_dst, filepath, FILE_MAX); - data->count_changed++; - return true; - } + /* so functions can check old value */ + BLI_strncpy(path_dst, path, FILE_MAX); - /* Failed to make relative path absolute. */ - BLI_assert(0); - BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src); - data->count_failed++; - return false; + if (bpath_data->callback_function(bpath_data, path_dst, path_src)) { + BLI_strncpy(path, path_dst, FILE_MAX); + return true; } - /* Absolute, leave this as-is. */ return false; } -void BKE_bpath_relative_rebase(Main *bmain, - const char *basedir_src, - const char *basedir_dst, - ReportList *reports) +bool BKE_bpath_foreach_path_dirfile_fixed_process(BPathForeachPathData *bpath_data, + char *path_dir, + char *path_file) { - BPathRebase_Data data = {NULL}; - const int flag = (BKE_BPATH_TRAVERSE_SKIP_LIBRARY | BKE_BPATH_TRAVERSE_SKIP_MULTIFILE); - - BLI_assert(basedir_src[0] != '\0'); - BLI_assert(basedir_dst[0] != '\0'); + const char *absolute_base_path = bpath_data->absolute_base_path; - data.basedir_src = basedir_src; - data.basedir_dst = basedir_dst; - data.reports = reports; - - BKE_bpath_traverse_main(bmain, bpath_relative_rebase_visit_cb, flag, (void *)&data); + char path_src[FILE_MAX]; + char path_dst[FILE_MAX]; - BKE_reportf(reports, - data.count_failed ? RPT_WARNING : RPT_INFO, - "Total files %d | Changed %d | Failed %d", - data.count_tot, - data.count_changed, - data.count_failed); -} + BLI_join_dirfile(path_src, sizeof(path_src), path_dir, path_file); -/** \} */ + /* So that functions can access the old value. */ + BLI_strncpy(path_dst, path_src, FILE_MAX); -/* -------------------------------------------------------------------- */ -/** \name Make Paths Relative - * \{ */ + if (absolute_base_path) { + BLI_path_abs(path_src, absolute_base_path); + } -typedef struct BPathRemap_Data { - const char *basedir; - ReportList *reports; + if (bpath_data->callback_function(bpath_data, path_dst, (const char *)path_src)) { + BLI_split_dirfile(path_dst, path_dir, path_file, FILE_MAXDIR, FILE_MAXFILE); + return true; + } - int count_tot; - int count_changed; - int count_failed; -} BPathRemap_Data; + return false; +} -static bool bpath_relative_convert_visit_cb(void *userdata, char *path_dst, const char *path_src) +bool BKE_bpath_foreach_path_allocated_process(BPathForeachPathData *bpath_data, char **path) { - BPathRemap_Data *data = (BPathRemap_Data *)userdata; + const char *absolute_base_path = bpath_data->absolute_base_path; - data->count_tot++; - - if (BLI_path_is_rel(path_src)) { - return false; /* already relative */ - } + char path_src_buf[FILE_MAX]; + const char *path_src; + char path_dst[FILE_MAX]; - strcpy(path_dst, path_src); - BLI_path_rel(path_dst, data->basedir); - if (BLI_path_is_rel(path_dst)) { - data->count_changed++; + if (absolute_base_path) { + BLI_strncpy(path_src_buf, *path, sizeof(path_src_buf)); + BLI_path_abs(path_src_buf, absolute_base_path); + path_src = path_src_buf; } else { - BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made relative", path_src); - data->count_failed++; + path_src = *path; } - return true; -} - -void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports) -{ - BPathRemap_Data data = {NULL}; - const int flag = BKE_BPATH_TRAVERSE_SKIP_LIBRARY; - if (basedir[0] == '\0') { - CLOG_ERROR(&LOG, "basedir='', this is a bug"); - return; + if (bpath_data->callback_function(bpath_data, path_dst, path_src)) { + MEM_freeN(*path); + (*path) = BLI_strdup(path_dst); + return true; } - data.basedir = basedir; - data.reports = reports; - - BKE_bpath_traverse_main(bmain, bpath_relative_convert_visit_cb, flag, (void *)&data); - - BKE_reportf(reports, - data.count_failed ? RPT_WARNING : RPT_INFO, - "Total files %d | Changed %d | Failed %d", - data.count_tot, - data.count_changed, - data.count_failed); + return false; } /** \} */ /* -------------------------------------------------------------------- */ -/** \name Make Paths Absolute +/** \name Check Missing Files * \{ */ -static bool bpath_absolute_convert_visit_cb(void *userdata, char *path_dst, const char *path_src) +static bool check_missing_files_foreach_path_cb(BPathForeachPathData *bpath_data, + char *UNUSED(path_dst), + const char *path_src) { - BPathRemap_Data *data = (BPathRemap_Data *)userdata; - - data->count_tot++; + ReportList *reports = (ReportList *)bpath_data->user_data; - if (BLI_path_is_rel(path_src) == false) { - return false; /* already absolute */ + if (!BLI_exists(path_src)) { + BKE_reportf(reports, RPT_WARNING, "Path '%s' not found", path_src); } - strcpy(path_dst, path_src); - BLI_path_abs(path_dst, data->basedir); - if (BLI_path_is_rel(path_dst) == false) { - data->count_changed++; - } - else { - BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src); - data->count_failed++; - } - return true; + return false; } -/* similar to BKE_bpath_relative_convert - keep in sync! */ -void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports) +void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports) { - BPathRemap_Data data = {NULL}; - const int flag = BKE_BPATH_TRAVERSE_SKIP_LIBRARY; - - if (basedir[0] == '\0') { - CLOG_ERROR(&LOG, "basedir='', this is a bug"); - return; - } - - data.basedir = basedir; - data.reports = reports; - - BKE_bpath_traverse_main(bmain, bpath_absolute_convert_visit_cb, flag, (void *)&data); - - BKE_reportf(reports, - data.count_failed ? RPT_WARNING : RPT_INFO, - "Total files %d | Changed %d | Failed %d", - data.count_tot, - data.count_changed, - data.count_failed); + BKE_bpath_foreach_path_main(&(BPathForeachPathData){ + .bmain = bmain, + .callback_function = check_missing_files_foreach_path_cb, + .flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED | + BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN | BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES, + .user_data = reports}); } /** \} */ @@ -302,72 +247,79 @@ void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *re /** \name Find Missing Files * \{ */ -/** - * find this file recursively, use the biggest file so thumbnails don't get used by mistake - * \param filename_new: the path will be copied here, caller must initialize as empty string. - * \param dirname: subdir to search - * \param filename: set this filename - * \param filesize: filesize for the file +#define MAX_DIR_RECURSE 16 +#define FILESIZE_INVALID_DIRECTORY -1 + +/** Find the given filename recursively in the given search directory and its sub-directories. + * + * \note Use the biggest matching file found, so that thumbnails don't get used by mistake. + * + * \param search_directory: Directory to search in. + * \param filename_src: Search for this filename. + * \param r_filename_new: The path of the new found file will be copied here, caller must + * initialize as empty string. + * \param r_filesize: Size of the file, `FILESIZE_INVALID_DIRECTORY` if search directory could not + * be opened. + * \param r_recurse_depth: Current recursion depth. * - * \returns found: 1/0. + * \return true if found, false otherwise. */ -#define MAX_RECUR 16 -static bool missing_files_find__recursive(char *filename_new, - const char *dirname, - const char *filename, +static bool missing_files_find__recursive(const char *search_directory, + const char *filename_src, + char r_filename_new[FILE_MAX], int64_t *r_filesize, - int *r_recur_depth) + int *r_recurse_depth) { - /* file searching stuff */ + /* TODO: Move this function to BLI_path_utils? The 'biggest size' behavior is quite specific + * though... */ DIR *dir; - struct dirent *de; BLI_stat_t status; char path[FILE_MAX]; int64_t size; bool found = false; - dir = opendir(dirname); + dir = opendir(search_directory); if (dir == NULL) { return found; } - if (*r_filesize == -1) { - *r_filesize = 0; /* dir opened fine */ + if (*r_filesize == FILESIZE_INVALID_DIRECTORY) { + *r_filesize = 0; /* The directory opened fine. */ } - while ((de = readdir(dir)) != NULL) { - + for (struct dirent *de = readdir(dir); de != NULL; de = readdir(dir)) { if (FILENAME_IS_CURRPAR(de->d_name)) { continue; } - BLI_join_dirfile(path, sizeof(path), dirname, de->d_name); + BLI_join_dirfile(path, sizeof(path), search_directory, de->d_name); if (BLI_stat(path, &status) == -1) { - continue; /* can't stat, don't bother with this file, could print debug info here */ + CLOG_WARN(&LOG, "Cannot get file status (`stat()`) of '%s'", path); + continue; } - if (S_ISREG(status.st_mode)) { /* is file */ - if (BLI_path_ncmp(filename, de->d_name, FILE_MAX) == 0) { /* name matches */ - /* open the file to read its size */ + if (S_ISREG(status.st_mode)) { /* It is a file. */ + if (BLI_path_ncmp(filename_src, de->d_name, FILE_MAX) == 0) { /* Names match. */ size = status.st_size; - if ((size > 0) && (size > *r_filesize)) { /* find the biggest file */ + if ((size > 0) && (size > *r_filesize)) { /* Find the biggest matching file. */ *r_filesize = size; - BLI_strncpy(filename_new, path, FILE_MAX); + BLI_strncpy(r_filename_new, path, FILE_MAX); found = true; } } } - else if (S_ISDIR(status.st_mode)) { /* is subdir */ - if (*r_recur_depth <= MAX_RECUR) { - (*r_recur_depth)++; + else if (S_ISDIR(status.st_mode)) { /* It is a sub-directory. */ + if (*r_recurse_depth <= MAX_DIR_RECURSE) { + (*r_recurse_depth)++; found |= missing_files_find__recursive( - filename_new, path, filename, r_filesize, r_recur_depth); - (*r_recur_depth)--; + path, filename_src, r_filename_new, r_filesize, r_recurse_depth); + (*r_recurse_depth)--; } } } + closedir(dir); return found; } @@ -376,37 +328,37 @@ typedef struct BPathFind_Data { const char *basedir; const char *searchdir; ReportList *reports; - bool find_all; + bool find_all; /* Also search for files which current path is still valid. */ } BPathFind_Data; -static bool missing_files_find__visit_cb(void *userdata, char *path_dst, const char *path_src) +static bool missing_files_find_foreach_path_cb(BPathForeachPathData *bpath_data, + char *path_dst, + const char *path_src) { - BPathFind_Data *data = (BPathFind_Data *)userdata; + BPathFind_Data *data = (BPathFind_Data *)bpath_data->user_data; char filename_new[FILE_MAX]; - int64_t filesize = -1; - int recur_depth = 0; - bool found; + int64_t filesize = FILESIZE_INVALID_DIRECTORY; + int recurse_depth = 0; + bool is_found; - if (data->find_all == false) { - if (BLI_exists(path_src)) { - return false; - } + if (!data->find_all && BLI_exists(path_src)) { + return false; } filename_new[0] = '\0'; - found = missing_files_find__recursive( - filename_new, data->searchdir, BLI_path_basename(path_src), &filesize, &recur_depth); + is_found = missing_files_find__recursive( + data->searchdir, BLI_path_basename(path_src), filename_new, &filesize, &recurse_depth); - if (filesize == -1) { /* could not open dir */ + if (filesize == FILESIZE_INVALID_DIRECTORY) { BKE_reportf(data->reports, RPT_WARNING, - "Could not open directory '%s'", + "Could not open the directory '%s'", BLI_path_basename(data->searchdir)); return false; } - if (found == false) { + if (is_found == false) { BKE_reportf(data->reports, RPT_WARNING, "Could not find '%s' in '%s'", @@ -419,7 +371,7 @@ static bool missing_files_find__visit_cb(void *userdata, char *path_dst, const c BLI_strncpy(path_dst, filename_new, FILE_MAX); - /* keep path relative if the previous one was relative */ + /* Keep the path relative if the previous one was relative. */ if (was_relative) { BLI_path_rel(path_dst, data->basedir); } @@ -433,480 +385,282 @@ void BKE_bpath_missing_files_find(Main *bmain, const bool find_all) { struct BPathFind_Data data = {NULL}; - const int flag = BKE_BPATH_TRAVERSE_ABS | BKE_BPATH_TRAVERSE_RELOAD_EDITED; + const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED | + BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN; data.basedir = BKE_main_blendfile_path(bmain); data.reports = reports; data.searchdir = searchpath; data.find_all = find_all; - BKE_bpath_traverse_main(bmain, missing_files_find__visit_cb, flag, (void *)&data); + BKE_bpath_foreach_path_main( + &(BPathForeachPathData){.bmain = bmain, + .callback_function = missing_files_find_foreach_path_cb, + .flag = flag, + .user_data = &data}); } +#undef MAX_DIR_RECURSE +#undef FILESIZE_INVALID_DIRECTORY + /** \} */ /* -------------------------------------------------------------------- */ -/** \name Generic File Path Traversal API +/** \name Rebase Relative Paths * \{ */ -/** - * Run a visitor on a string, replacing the contents of the string as needed. - */ -static bool rewrite_path_fixed(char *path, - BPathVisitor visit_cb, - const char *absbase, - void *userdata) +typedef struct BPathRebase_Data { + const char *basedir_src; + const char *basedir_dst; + ReportList *reports; + + int count_tot; + int count_changed; + int count_failed; +} BPathRebase_Data; + +static bool relative_rebase_foreach_path_cb(BPathForeachPathData *bpath_data, + char *path_dst, + const char *path_src) { - char path_src_buf[FILE_MAX]; - const char *path_src; - char path_dst[FILE_MAX]; + BPathRebase_Data *data = (BPathRebase_Data *)bpath_data->user_data; - if (absbase) { - BLI_strncpy(path_src_buf, path, sizeof(path_src_buf)); - BLI_path_abs(path_src_buf, absbase); - path_src = path_src_buf; + data->count_tot++; + + if (!BLI_path_is_rel(path_src)) { + /* Absolute, leave this as-is. */ + return false; } - else { - path_src = path; + + char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE]; + BLI_strncpy(filepath, path_src, FILE_MAX); + if (!BLI_path_abs(filepath, data->basedir_src)) { + BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src); + data->count_failed++; + return false; } - /* so functions can check old value */ - BLI_strncpy(path_dst, path, FILE_MAX); + BLI_path_normalize(NULL, filepath); - if (visit_cb(userdata, path_dst, path_src)) { - BLI_strncpy(path, path_dst, FILE_MAX); - return true; - } + /* This may fail, if so it's fine to leave absolute since the path is still valid. */ + BLI_path_rel(filepath, data->basedir_dst); - return false; + BLI_strncpy(path_dst, filepath, FILE_MAX); + data->count_changed++; + return true; } -static bool rewrite_path_fixed_dirfile(char path_dir[FILE_MAXDIR], - char path_file[FILE_MAXFILE], - BPathVisitor visit_cb, - const char *absbase, - void *userdata) +void BKE_bpath_relative_rebase(Main *bmain, + const char *basedir_src, + const char *basedir_dst, + ReportList *reports) { - char path_src[FILE_MAX]; - char path_dst[FILE_MAX]; - - BLI_join_dirfile(path_src, sizeof(path_src), path_dir, path_file); + BPathRebase_Data data = {NULL}; + const int flag = (BKE_BPATH_FOREACH_PATH_SKIP_LINKED | BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE); - /* so functions can check old value */ - BLI_strncpy(path_dst, path_src, FILE_MAX); + BLI_assert(basedir_src[0] != '\0'); + BLI_assert(basedir_dst[0] != '\0'); - if (absbase) { - BLI_path_abs(path_src, absbase); - } + data.basedir_src = basedir_src; + data.basedir_dst = basedir_dst; + data.reports = reports; - if (visit_cb(userdata, path_dst, (const char *)path_src)) { - BLI_split_dirfile(path_dst, path_dir, path_file, FILE_MAXDIR, FILE_MAXFILE); - return true; - } + BKE_bpath_foreach_path_main( + &(BPathForeachPathData){.bmain = bmain, + .callback_function = relative_rebase_foreach_path_cb, + .flag = flag, + .user_data = &data}); - return false; + BKE_reportf(reports, + data.count_failed ? RPT_WARNING : RPT_INFO, + "Total files %d | Changed %d | Failed %d", + data.count_tot, + data.count_changed, + data.count_failed); } -static bool rewrite_path_alloc(char **path, - BPathVisitor visit_cb, - const char *absbase, - void *userdata) -{ - char path_src_buf[FILE_MAX]; - const char *path_src; - char path_dst[FILE_MAX]; - - if (absbase) { - BLI_strncpy(path_src_buf, *path, sizeof(path_src_buf)); - BLI_path_abs(path_src_buf, absbase); - path_src = path_src_buf; - } - else { - path_src = *path; - } +/** \} */ - if (visit_cb(userdata, path_dst, path_src)) { - MEM_freeN(*path); - (*path) = BLI_strdup(path_dst); - return true; - } +/* -------------------------------------------------------------------- */ +/** \name Make Paths Relative Or Absolute + * \{ */ - return false; -} +typedef struct BPathRemap_Data { + const char *basedir; + ReportList *reports; -typedef struct Seq_callback_data { - const char *absbase; - void *bpath_user_data; - BPathVisitor visit_cb; - const int flag; -} Seq_callback_data; + int count_tot; + int count_changed; + int count_failed; +} BPathRemap_Data; -static bool seq_rewrite_path_callback(Sequence *seq, void *user_data) +static bool relative_convert_foreach_path_cb(BPathForeachPathData *bpath_data, + char *path_dst, + const char *path_src) { - if (SEQ_HAS_PATH(seq)) { - StripElem *se = seq->strip->stripdata; - Seq_callback_data *cd = (Seq_callback_data *)user_data; + BPathRemap_Data *data = (BPathRemap_Data *)bpath_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); - } + data->count_tot++; - 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); - } + if (BLI_path_is_rel(path_src)) { + return false; /* Already relative. */ + } + + BLI_strncpy(path_dst, path_src, FILE_MAX); + BLI_path_rel(path_dst, data->basedir); + if (BLI_path_is_rel(path_dst)) { + data->count_changed++; + } + else { + BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made relative", path_src); + data->count_failed++; } return true; } -/** - * Run visitor function 'visit' on all paths contained in 'id'. - */ -void BKE_bpath_traverse_id( - Main *bmain, ID *id, BPathVisitor visit_cb, const int flag, void *bpath_user_data) +static bool absolute_convert_foreach_path_cb(BPathForeachPathData *bpath_data, + char *path_dst, + const char *path_src) { - const char *absbase = (flag & BKE_BPATH_TRAVERSE_ABS) ? ID_BLEND_PATH(bmain, id) : NULL; + BPathRemap_Data *data = (BPathRemap_Data *)bpath_data->user_data; - if ((flag & BKE_BPATH_TRAVERSE_SKIP_LIBRARY) && ID_IS_LINKED(id)) { - return; - } + data->count_tot++; - if (id->library_weak_reference != NULL) { - rewrite_path_fixed( - id->library_weak_reference->library_filepath, visit_cb, absbase, bpath_user_data); + if (!BLI_path_is_rel(path_src)) { + return false; /* Already absolute. */ } - switch (GS(id->name)) { - case ID_IM: { - Image *ima; - ima = (Image *)id; - if (BKE_image_has_packedfile(ima) == false || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) { - /* Skip empty file paths, these are typically from generated images and - * don't make sense to add directories to until the image has been saved - * once to give it a meaningful value. */ - if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED) && - ima->filepath[0]) { - if (rewrite_path_fixed(ima->filepath, visit_cb, absbase, bpath_user_data)) { - if (flag & BKE_BPATH_TRAVERSE_RELOAD_EDITED) { - if (!BKE_image_has_packedfile(ima) && - /* image may have been painted onto (and not saved, T44543) */ - !BKE_image_is_dirty(ima)) { - BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_RELOAD); - } - } - } - } - } - break; - } - case ID_BR: { - Brush *brush = (Brush *)id; - if (brush->icon_filepath[0]) { - rewrite_path_fixed(brush->icon_filepath, visit_cb, absbase, bpath_user_data); - } - break; - } - case ID_OB: { - Object *ob = (Object *)id; - ModifierData *md; - ParticleSystem *psys; - -#define BPATH_TRAVERSE_POINTCACHE(ptcaches) \ - { \ - PointCache *cache; \ - for (cache = (ptcaches).first; cache; cache = cache->next) { \ - if (cache->flag & PTCACHE_DISK_CACHE) { \ - rewrite_path_fixed(cache->path, visit_cb, absbase, bpath_user_data); \ - } \ - } \ - } \ - (void)0 - - for (md = ob->modifiers.first; md; md = md->next) { - if (md->type == eModifierType_Fluidsim) { - FluidsimModifierData *fluidmd = (FluidsimModifierData *)md; - if (fluidmd->fss) { - rewrite_path_fixed(fluidmd->fss->surfdataPath, visit_cb, absbase, bpath_user_data); - } - } - else if (md->type == eModifierType_Fluid) { - FluidModifierData *fmd = (FluidModifierData *)md; - if (fmd->type & MOD_FLUID_TYPE_DOMAIN && fmd->domain) { - rewrite_path_fixed(fmd->domain->cache_directory, visit_cb, absbase, bpath_user_data); - } - } - else if (md->type == eModifierType_Cloth) { - ClothModifierData *clmd = (ClothModifierData *)md; - BPATH_TRAVERSE_POINTCACHE(clmd->ptcaches); - } - else if (md->type == eModifierType_Ocean) { - OceanModifierData *omd = (OceanModifierData *)md; - rewrite_path_fixed(omd->cachepath, visit_cb, absbase, bpath_user_data); - } - else if (md->type == eModifierType_MeshCache) { - MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md; - rewrite_path_fixed(mcmd->filepath, visit_cb, absbase, bpath_user_data); - } - } - - if (ob->soft) { - BPATH_TRAVERSE_POINTCACHE(ob->soft->shared->ptcaches); - } - - for (psys = ob->particlesystem.first; psys; psys = psys->next) { - BPATH_TRAVERSE_POINTCACHE(psys->ptcaches); - } - -#undef BPATH_TRAVERSE_POINTCACHE - - break; - } - case ID_SO: { - bSound *sound = (bSound *)id; - if (sound->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) { - rewrite_path_fixed(sound->filepath, visit_cb, absbase, bpath_user_data); - } - break; - } - case ID_VO: { - Volume *volume = (Volume *)id; - if (volume->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) { - rewrite_path_fixed(volume->filepath, visit_cb, absbase, bpath_user_data); - } - break; - } - case ID_TXT: - if (((Text *)id)->filepath) { - rewrite_path_alloc(&((Text *)id)->filepath, visit_cb, absbase, bpath_user_data); - } - break; - case ID_VF: { - VFont *vfont = (VFont *)id; - if (vfont->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) { - if (BKE_vfont_is_builtin(vfont) == false) { - rewrite_path_fixed(((VFont *)id)->filepath, visit_cb, absbase, bpath_user_data); - } - } - break; - } - case ID_MA: { - Material *ma = (Material *)id; - bNodeTree *ntree = ma->nodetree; - - if (ntree) { - bNode *node; - - for (node = ntree->nodes.first; node; node = node->next) { - if (node->type == SH_NODE_SCRIPT) { - NodeShaderScript *nss = (NodeShaderScript *)node->storage; - rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data); - } - else if (node->type == SH_NODE_TEX_IES) { - NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage; - rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data); - } - } - } - break; - } - case ID_NT: { - bNodeTree *ntree = (bNodeTree *)id; - bNode *node; - - if (ntree->type == NTREE_SHADER) { - /* same as lines above */ - for (node = ntree->nodes.first; node; node = node->next) { - if (node->type == SH_NODE_SCRIPT) { - NodeShaderScript *nss = (NodeShaderScript *)node->storage; - rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data); - } - else if (node->type == SH_NODE_TEX_IES) { - NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage; - rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data); - } - } - } - break; - } - case ID_SCE: { - Scene *scene = (Scene *)id; - if (scene->ed) { - 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; - } - case ID_ME: { - Mesh *me = (Mesh *)id; - if (me->ldata.external) { - rewrite_path_fixed(me->ldata.external->filename, visit_cb, absbase, bpath_user_data); - } - break; - } - case ID_LI: { - Library *lib = (Library *)id; - /* keep packedfile paths always relative to the blend */ - if (lib->packedfile == NULL) { - if (rewrite_path_fixed(lib->filepath, visit_cb, absbase, bpath_user_data)) { - BKE_library_filepath_set(bmain, lib, lib->filepath); - } - } - break; - } - case ID_MC: { - MovieClip *clip = (MovieClip *)id; - rewrite_path_fixed(clip->filepath, visit_cb, absbase, bpath_user_data); - break; - } - case ID_CF: { - CacheFile *cache_file = (CacheFile *)id; - rewrite_path_fixed(cache_file->filepath, visit_cb, absbase, bpath_user_data); - break; - } - default: - /* Nothing to do for other IDs that don't contain file paths. */ - break; + BLI_strncpy(path_dst, path_src, FILENAME_MAX); + BLI_path_abs(path_dst, data->basedir); + if (BLI_path_is_rel(path_dst) == false) { + data->count_changed++; + } + else { + BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src); + data->count_failed++; } + return true; } -void BKE_bpath_traverse_id_list( - Main *bmain, ListBase *lb, BPathVisitor visit_cb, const int flag, void *bpath_user_data) +static void bpath_absolute_relative_convert(Main *bmain, + const char *basedir, + ReportList *reports, + BPathForeachPathFunctionCallback callback_function) { - ID *id; - for (id = lb->first; id; id = id->next) { - BKE_bpath_traverse_id(bmain, id, visit_cb, flag, bpath_user_data); + BPathRemap_Data data = {NULL}; + const int flag = BKE_BPATH_FOREACH_PATH_SKIP_LINKED; + + BLI_assert(basedir[0] != '\0'); + if (basedir[0] == '\0') { + CLOG_ERROR(&LOG, "basedir='', this is a bug"); + return; } + + data.basedir = basedir; + data.reports = reports; + + BKE_bpath_foreach_path_main(&(BPathForeachPathData){ + .bmain = bmain, .callback_function = callback_function, .flag = flag, .user_data = &data}); + + BKE_reportf(reports, + data.count_failed ? RPT_WARNING : RPT_INFO, + "Total files %d | Changed %d | Failed %d", + data.count_tot, + data.count_changed, + data.count_failed); } -void BKE_bpath_traverse_main(Main *bmain, - BPathVisitor visit_cb, - const int flag, - void *bpath_user_data) +void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports) { - ListBase *lbarray[INDEX_ID_MAX]; - int a = set_listbasepointers(bmain, lbarray); - while (a--) { - BKE_bpath_traverse_id_list(bmain, lbarray[a], visit_cb, flag, bpath_user_data); - } + bpath_absolute_relative_convert(bmain, basedir, reports, relative_convert_foreach_path_cb); } -/** - * Rewrites a relative path to be relative to the main file - unless the path is - * absolute, in which case it is not altered. - */ -bool BKE_bpath_relocate_visitor(void *pathbase_v, char *path_dst, const char *path_src) +void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports) { - /* be sure there is low chance of the path being too short */ - char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE]; - const char *base_new = ((char **)pathbase_v)[0]; - const char *base_old = ((char **)pathbase_v)[1]; - - if (BLI_path_is_rel(base_old)) { - CLOG_ERROR(&LOG, "old base path '%s' is not absolute.", base_old); - return false; - } - - /* Make referenced file absolute. This would be a side-effect of - * BLI_path_normalize, but we do it explicitly so we know if it changed. */ - BLI_strncpy(filepath, path_src, FILE_MAX); - if (BLI_path_abs(filepath, base_old)) { - /* Path was relative and is now absolute. Remap. - * Important BLI_path_normalize runs before the path is made relative - * because it won't work for paths that start with "//../" */ - BLI_path_normalize(base_new, filepath); - BLI_path_rel(filepath, base_new); - BLI_strncpy(path_dst, filepath, FILE_MAX); - return true; - } - - /* Path was not relative to begin with. */ - return false; + bpath_absolute_relative_convert(bmain, basedir, reports, absolute_convert_foreach_path_cb); } /** \} */ /* -------------------------------------------------------------------- */ -/** \name Backup/Restore/Free functions, +/** \name Backup/Restore/Free paths list functions. * - * \note These functions assume the data won't change order. * \{ */ struct PathStore { struct PathStore *next, *prev; }; -static bool bpath_list_append(void *userdata, char *UNUSED(path_dst), const char *path_src) +static bool bpath_list_append(BPathForeachPathData *bpath_data, + char *UNUSED(path_dst), + const char *path_src) { - /* store the path and string in a single alloc */ - ListBase *ls = userdata; + ListBase *path_list = bpath_data->user_data; size_t path_size = strlen(path_src) + 1; + + /* NOTE: the PathStore and its string are allocated together in a single alloc. */ struct PathStore *path_store = MEM_mallocN(sizeof(struct PathStore) + path_size, __func__); char *filepath = (char *)(path_store + 1); - memcpy(filepath, path_src, path_size); - BLI_addtail(ls, path_store); + BLI_strncpy(filepath, path_src, path_size); + BLI_addtail(path_list, path_store); return false; } -static bool bpath_list_restore(void *userdata, char *path_dst, const char *path_src) +static bool bpath_list_restore(BPathForeachPathData *bpath_data, + char *path_dst, + const char *path_src) { - /* assume ls->first won't be NULL because the number of paths can't change! - * (if they do caller is wrong) */ - ListBase *ls = userdata; - struct PathStore *path_store = ls->first; + ListBase *path_list = bpath_data->user_data; + + /* `ls->first` should never be NULL, because the number of paths should not change. + * If this happens, there is a bug in caller code. */ + BLI_assert(!BLI_listbase_is_empty(path_list)); + + struct PathStore *path_store = path_list->first; const char *filepath = (char *)(path_store + 1); - bool ret; + bool is_path_changed = false; - if (STREQ(path_src, filepath)) { - ret = false; - } - else { + if (!STREQ(path_src, filepath)) { BLI_strncpy(path_dst, filepath, FILE_MAX); - ret = true; + is_path_changed = true; } - BLI_freelinkN(ls, path_store); - return ret; + BLI_freelinkN(path_list, path_store); + return is_path_changed; } -/* return ls_handle */ -void *BKE_bpath_list_backup(Main *bmain, const int flag) +void *BKE_bpath_list_backup(Main *bmain, const eBPathForeachFlag flag) { - ListBase *ls = MEM_callocN(sizeof(ListBase), __func__); + ListBase *path_list = MEM_callocN(sizeof(ListBase), __func__); - BKE_bpath_traverse_main(bmain, bpath_list_append, flag, ls); + BKE_bpath_foreach_path_main(&(BPathForeachPathData){.bmain = bmain, + .callback_function = bpath_list_append, + .flag = flag, + .user_data = path_list}); - return ls; + return path_list; } -void BKE_bpath_list_restore(Main *bmain, const int flag, void *ls_handle) +void BKE_bpath_list_restore(Main *bmain, const eBPathForeachFlag flag, void *path_list_handle) { - ListBase *ls = ls_handle; + ListBase *path_list = path_list_handle; - BKE_bpath_traverse_main(bmain, bpath_list_restore, flag, ls); + BKE_bpath_foreach_path_main(&(BPathForeachPathData){.bmain = bmain, + .callback_function = bpath_list_restore, + .flag = flag, + .user_data = path_list}); } -void BKE_bpath_list_free(void *ls_handle) +void BKE_bpath_list_free(void *path_list_handle) { - ListBase *ls = ls_handle; - BLI_assert(BLI_listbase_is_empty(ls)); /* assumes we were used */ - BLI_freelistN(ls); - MEM_freeN(ls); + ListBase *path_list = path_list_handle; + /* The whole list should have been consumed by #BKE_bpath_list_restore, see also comment in + * #bpath_list_restore. */ + BLI_assert(BLI_listbase_is_empty(path_list)); + + BLI_freelistN(path_list); + MEM_freeN(path_list); } /** \} */ diff --git a/source/blender/blenkernel/intern/bpath_test.cc b/source/blender/blenkernel/intern/bpath_test.cc new file mode 100644 index 00000000000..121d47af75f --- /dev/null +++ b/source/blender/blenkernel/intern/bpath_test.cc @@ -0,0 +1,181 @@ +/* + * 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 by Blender Foundation. + */ +#include "testing/testing.h" + +#include "CLG_log.h" + +#include "BKE_bpath.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" + +#include "MEM_guardedalloc.h" + +#include "DNA_listBase.h" +#include "DNA_movieclip_types.h" +#include "DNA_text_types.h" + +#include "BLI_listbase.h" +#include "BLI_path_util.h" +#include "BLI_string.h" + +namespace blender::bke::tests { + +#ifdef WIN32 +# define ABSOLUTE_ROOT "C:" SEP_STR +#else +# define ABSOLUTE_ROOT SEP_STR +#endif + +#define RELATIVE_ROOT "//" +#define BASE_DIR ABSOLUTE_ROOT "blendfiles" SEP_STR +#define REBASE_DIR BASE_DIR "rebase" SEP_STR + +#define BLENDFILE_NAME "bpath.blend" +#define BLENDFILE_PATH BASE_DIR BLENDFILE_NAME + +#define TEXT_PATH_ITEM "texts" SEP_STR "text.txt" +#define TEXT_PATH_ABSOLUTE ABSOLUTE_ROOT TEXT_PATH_ITEM +#define TEXT_PATH_ABSOLUTE_MADE_RELATIVE RELATIVE_ROOT ".." SEP_STR TEXT_PATH_ITEM +#define TEXT_PATH_RELATIVE RELATIVE_ROOT TEXT_PATH_ITEM +#define TEXT_PATH_RELATIVE_MADE_ABSOLUTE BASE_DIR TEXT_PATH_ITEM + +#define MOVIECLIP_PATH_ITEM "movieclips" SEP_STR "movieclip.avi" +#define MOVIECLIP_PATH_ABSOLUTE ABSOLUTE_ROOT MOVIECLIP_PATH_ITEM +#define MOVIECLIP_PATH_ABSOLUTE_MADE_RELATIVE RELATIVE_ROOT ".." SEP_STR MOVIECLIP_PATH_ITEM +#define MOVIECLIP_PATH_RELATIVE RELATIVE_ROOT MOVIECLIP_PATH_ITEM +#define MOVIECLIP_PATH_RELATIVE_MADE_ABSOLUTE BASE_DIR MOVIECLIP_PATH_ITEM + +class BPathTest : public testing::Test { + public: + static void SetUpTestSuite() + { + CLG_init(); + BKE_idtype_init(); + } + static void TearDownTestSuite() + { + CLG_exit(); + } + + void SetUp() override + { + bmain = BKE_main_new(); + STRNCPY(bmain->filepath, BLENDFILE_PATH); + + BKE_id_new(bmain, ID_TXT, nullptr); + BKE_id_new(bmain, ID_MC, nullptr); + } + + void TearDown() override + { + BKE_main_free(bmain); + } + + Main *bmain; +}; + +TEST_F(BPathTest, rebase_on_relative) +{ + // Test on relative paths, should be modified. + Text *text = reinterpret_cast<Text *>(bmain->texts.first); + text->filepath = BLI_strdup(TEXT_PATH_RELATIVE); + + MovieClip *movie_clip = reinterpret_cast<MovieClip *>(bmain->movieclips.first); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_RELATIVE, sizeof(movie_clip->filepath)); + + BKE_bpath_relative_rebase(bmain, BASE_DIR, REBASE_DIR, nullptr); + + EXPECT_STREQ(text->filepath, RELATIVE_ROOT ".." SEP_STR TEXT_PATH_ITEM); + EXPECT_STREQ(movie_clip->filepath, RELATIVE_ROOT ".." SEP_STR MOVIECLIP_PATH_ITEM); +} + +TEST_F(BPathTest, rebase_on_absolute) +{ + // Test on absolute paths, should not be modified. + Text *text = reinterpret_cast<Text *>(bmain->texts.first); + text->filepath = BLI_strdup(TEXT_PATH_ABSOLUTE); + + MovieClip *movie_clip = reinterpret_cast<MovieClip *>(bmain->movieclips.first); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE, sizeof(movie_clip->filepath)); + + BKE_bpath_relative_rebase(bmain, BASE_DIR, REBASE_DIR, nullptr); + + EXPECT_STREQ(text->filepath, TEXT_PATH_ABSOLUTE); + EXPECT_STREQ(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE); +} + +TEST_F(BPathTest, convert_to_relative) +{ + Text *text = reinterpret_cast<Text *>(bmain->texts.first); + text->filepath = BLI_strdup(TEXT_PATH_RELATIVE); + + MovieClip *movie_clip = reinterpret_cast<MovieClip *>(bmain->movieclips.first); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE, sizeof(movie_clip->filepath)); + + BKE_bpath_relative_convert(bmain, BASE_DIR, nullptr); + + // Already relative path should not be modified. + EXPECT_STREQ(text->filepath, TEXT_PATH_RELATIVE); + // Absolute path should be modified. + EXPECT_STREQ(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE_MADE_RELATIVE); +} + +TEST_F(BPathTest, convert_to_absolute) +{ + Text *text = reinterpret_cast<Text *>(bmain->texts.first); + text->filepath = BLI_strdup(TEXT_PATH_RELATIVE); + + MovieClip *movie_clip = reinterpret_cast<MovieClip *>(bmain->movieclips.first); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE, sizeof(movie_clip->filepath)); + + BKE_bpath_absolute_convert(bmain, BASE_DIR, nullptr); + + // Relative path should be modified. + EXPECT_STREQ(text->filepath, TEXT_PATH_RELATIVE_MADE_ABSOLUTE); + // Already absolute path should not be modified. + EXPECT_STREQ(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE); +} + +TEST_F(BPathTest, list_backup_restore) +{ + Text *text = reinterpret_cast<Text *>(bmain->texts.first); + text->filepath = BLI_strdup(TEXT_PATH_RELATIVE); + + MovieClip *movie_clip = reinterpret_cast<MovieClip *>(bmain->movieclips.first); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE, sizeof(movie_clip->filepath)); + + void *path_list_handle = BKE_bpath_list_backup(bmain, static_cast<eBPathForeachFlag>(0)); + + ListBase *path_list = reinterpret_cast<ListBase *>(path_list_handle); + EXPECT_EQ(BLI_listbase_count(path_list), 2); + + MEM_freeN(text->filepath); + text->filepath = BLI_strdup(TEXT_PATH_ABSOLUTE); + BLI_strncpy(movie_clip->filepath, MOVIECLIP_PATH_RELATIVE, sizeof(movie_clip->filepath)); + + BKE_bpath_list_restore(bmain, static_cast<eBPathForeachFlag>(0), path_list_handle); + + EXPECT_STREQ(text->filepath, TEXT_PATH_RELATIVE); + EXPECT_STREQ(movie_clip->filepath, MOVIECLIP_PATH_ABSOLUTE); + EXPECT_EQ(BLI_listbase_count(path_list), 0); + + BKE_bpath_list_free(path_list_handle); +} + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index d70b941695e..c86d4658cc9 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -33,6 +33,7 @@ #include "BLT_translation.h" +#include "BKE_bpath.h" #include "BKE_brush.h" #include "BKE_colortools.h" #include "BKE_context.h" @@ -148,16 +149,9 @@ static void brush_make_local(Main *bmain, ID *id, const int flags) 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) - * - only local users: set flag - * - mixed: make copy - */ + bool force_local, force_copy; + BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy); if (brush->clone.image) { /* Special case: ima always local immediately. Clone image should only have one user anyway. */ @@ -170,21 +164,9 @@ static void brush_make_local(Main *bmain, ID *id, const int flags) 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; - } - } - } - if (force_local) { - BKE_lib_id_clear_library_data(bmain, &brush->id); - BKE_lib_id_expand_local(bmain, &brush->id); + 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); @@ -207,14 +189,23 @@ 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_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_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Brush *brush = (Brush *)id; + if (brush->icon_filepath[0] != '\0') { + BKE_bpath_foreach_path_fixed_process(bpath_data, brush->icon_filepath); } - BKE_texture_mtex_foreach_id(data, &brush->mtex); - BKE_texture_mtex_foreach_id(data, &brush->mask_mtex); } static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_address) @@ -413,6 +404,7 @@ IDTypeInfo IDType_ID_BR = { .name_plural = "brushes", .translation_context = BLT_I18NCONTEXT_ID_BRUSH, .flags = IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = brush_init_data, .copy_data = brush_copy_data, @@ -420,6 +412,7 @@ IDTypeInfo IDType_ID_BR = { .make_local = brush_make_local, .foreach_id = brush_foreach_id, .foreach_cache = NULL, + .foreach_path = brush_foreach_path, .owner_get = NULL, .blend_write = brush_blend_write, @@ -502,10 +495,6 @@ static void brush_defaults(Brush *brush) /* Datablock add/copy/free/make_local */ -/** - * \note Resulting brush will have two users: one as a fake user, - * another is assumed to be used by the caller. - */ Brush *BKE_brush_add(Main *bmain, const char *name, const eObjectMode ob_mode) { Brush *brush; @@ -517,7 +506,6 @@ Brush *BKE_brush_add(Main *bmain, const char *name, const eObjectMode ob_mode) return brush; } -/* add grease pencil settings */ void BKE_brush_init_gpencil_settings(Brush *brush) { if (brush->gpencil_settings == NULL) { @@ -545,7 +533,6 @@ void BKE_brush_init_gpencil_settings(Brush *brush) brush->gpencil_settings->curve_rand_value = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); } -/* add a new gp-brush */ Brush *BKE_brush_add_gpencil(Main *bmain, ToolSettings *ts, const char *name, eObjectMode mode) { Paint *paint = NULL; @@ -585,7 +572,6 @@ Brush *BKE_brush_add_gpencil(Main *bmain, ToolSettings *ts, const char *name, eO return brush; } -/* Delete a Brush. */ bool BKE_brush_delete(Main *bmain, Brush *brush) { if (brush->id.tag & LIB_TAG_INDIRECT) { @@ -1319,7 +1305,6 @@ static Brush *gpencil_brush_ensure( return brush; } -/* Create a set of grease pencil Drawing presets. */ void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool reset) { bool r_new = false; @@ -1421,7 +1406,6 @@ void BKE_brush_gpencil_paint_presets(Main *bmain, ToolSettings *ts, const bool r } } -/* Create a set of grease pencil Vertex Paint presets. */ void BKE_brush_gpencil_vertex_presets(Main *bmain, ToolSettings *ts, const bool reset) { bool r_new = false; @@ -1468,7 +1452,6 @@ void BKE_brush_gpencil_vertex_presets(Main *bmain, ToolSettings *ts, const bool } } -/* Create a set of grease pencil Sculpt Paint presets. */ void BKE_brush_gpencil_sculpt_presets(Main *bmain, ToolSettings *ts, const bool reset) { bool r_new = false; @@ -1543,7 +1526,6 @@ void BKE_brush_gpencil_sculpt_presets(Main *bmain, ToolSettings *ts, const bool } } -/* Create a set of grease pencil Weight Paint presets. */ void BKE_brush_gpencil_weight_presets(Main *bmain, ToolSettings *ts, const bool reset) { bool r_new = false; @@ -1945,9 +1927,6 @@ void BKE_brush_sculpt_reset(Brush *br) } } -/** - * Library Operations - */ void BKE_brush_curve_preset(Brush *b, eCurveMappingPreset preset) { CurveMapping *cumap = NULL; @@ -1965,10 +1944,6 @@ void BKE_brush_curve_preset(Brush *b, eCurveMappingPreset preset) BKE_curvemapping_changed(cumap, false); } -/* Generic texture sampler for 3D painting systems. point has to be either in - * region space mouse coordinates, or 3d world coordinates for 3D mapping. - * - * rgba outputs straight alpha. */ float BKE_brush_sample_tex_3d(const Scene *scene, const Brush *br, const float point[3], @@ -2361,7 +2336,6 @@ void BKE_brush_weight_set(const Scene *scene, Brush *brush, float value) } } -/* scale unprojected radius to reflect a change in the brush's 2D size */ void BKE_brush_scale_unprojected_radius(float *unprojected_radius, int new_brush_size, int old_brush_size) @@ -2374,7 +2348,6 @@ void BKE_brush_scale_unprojected_radius(float *unprojected_radius, (*unprojected_radius) *= scale; } -/* scale brush size to reflect a change in the brush's unprojected radius */ void BKE_brush_scale_size(int *r_brush_size, float new_unprojected_radius, float old_unprojected_radius) @@ -2425,7 +2398,6 @@ void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask) } } -/* Uses the brush curve control to find a strength value */ float BKE_brush_curve_strength(const Brush *br, float p, const float len) { float strength = 1.0f; @@ -2473,8 +2445,7 @@ float BKE_brush_curve_strength(const Brush *br, float p, const float len) return strength; } -/* Uses the brush curve control to find a strength value between 0 and 1 */ -float BKE_brush_curve_strength_clamped(Brush *br, float p, const float len) +float BKE_brush_curve_strength_clamped(const Brush *br, float p, const float len) { float strength = BKE_brush_curve_strength(br, p, len); @@ -2516,7 +2487,6 @@ unsigned int *BKE_brush_gen_texture_cache(Brush *br, int half_side, bool use_sec return texcache; } -/**** Radial Control ****/ struct ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool display_gradient) { ImBuf *im = MEM_callocN(sizeof(ImBuf), "radial control texture"); diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index 707201207d9..5e7a4eea0cd 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -125,9 +125,9 @@ bool bvhcache_has_tree(const BVHCache *bvh_cache, const BVHTree *tree) return false; } -BVHCache *bvhcache_init(void) +BVHCache *bvhcache_init() { - BVHCache *cache = (BVHCache *)MEM_callocN(sizeof(BVHCache), __func__); + BVHCache *cache = MEM_cnew<BVHCache>(__func__); BLI_mutex_init(&cache->mutex); return cache; } @@ -147,9 +147,6 @@ static void bvhcache_insert(BVHCache *bvh_cache, BVHTree *tree, BVHCacheType typ item->is_filled = true; } -/** - * frees a bvhcache - */ void bvhcache_free(BVHCache *bvh_cache) { for (int index = 0; index < BVHTREE_MAX_ITEM; index++) { @@ -161,9 +158,11 @@ void bvhcache_free(BVHCache *bvh_cache) 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); @@ -182,6 +181,7 @@ static void bvhtree_balance(BVHTree *tree, const bool isolate) } /** \} */ + /* -------------------------------------------------------------------- */ /** \name Local Callbacks * \{ */ @@ -233,8 +233,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], @@ -325,8 +329,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, @@ -430,8 +438,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], @@ -491,8 +503,12 @@ static void editmesh_verts_spherecast(void *userdata, 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, @@ -504,8 +520,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, @@ -647,7 +667,6 @@ static void bvhtree_from_mesh_verts_setup_data(BVHTreeFromMesh *data, data->vert_allocated = vert_allocated; } -/* 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, @@ -703,13 +722,6 @@ BVHTree *bvhtree_from_editmesh_verts( 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 `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 - * (else will be computed from mask). - */ BVHTree *bvhtree_from_mesh_verts_ex(BVHTreeFromMesh *data, const MVert *vert, const int verts_num, @@ -860,7 +872,6 @@ 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 */ BVHTree *bvhtree_from_editmesh_edges_ex(BVHTreeFromEditMesh *data, BMEditMesh *em, const BLI_bitmap *edges_mask, @@ -915,14 +926,6 @@ BVHTree *bvhtree_from_editmesh_edges( data, em, nullptr, -1, epsilon, tree_type, axis, BVHTREE_FROM_VERTS, nullptr, nullptr); } -/** - * 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 - * (else will be computed from mask). - */ BVHTree *bvhtree_from_mesh_edges_ex(BVHTreeFromMesh *data, const MVert *vert, const bool vert_allocated, @@ -1049,15 +1052,6 @@ static void bvhtree_from_mesh_faces_setup_data(BVHTreeFromMesh *data, data->face_allocated = face_allocated; } -/** - * 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 - * (else will be computed from mask). - */ BVHTree *bvhtree_from_mesh_faces_ex(BVHTreeFromMesh *data, const MVert *vert, const bool vert_allocated, @@ -1135,7 +1129,7 @@ static BVHTree *bvhtree_from_editmesh_looptri_create_tree(float epsilon, if (tree) { 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. */ @@ -1231,9 +1225,6 @@ static void bvhtree_from_mesh_looptri_setup_data(BVHTreeFromMesh *data, data->looptri_allocated = looptri_allocated; } -/** - * Builds a bvh tree where nodes are the looptri faces of the given bm - */ BVHTree *bvhtree_from_editmesh_looptri_ex(BVHTreeFromEditMesh *data, BMEditMesh *em, const BLI_bitmap *looptri_mask, @@ -1288,11 +1279,6 @@ BVHTree *bvhtree_from_editmesh_looptri( 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 mesh. - * - * \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, const bool vert_allocated, @@ -1435,12 +1421,6 @@ static BLI_bitmap *looptri_no_hidden_map_get(const MPoly *mpoly, return looptri_mask; } -/** - * 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, const struct Mesh *mesh, const BVHCacheType bvh_cache_type, @@ -1621,12 +1601,11 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data, memset(data, 0, sizeof(*data)); } + data->vert_normals = BKE_mesh_vertex_normals_ensure(mesh); + return tree; } -/** - * Builds or queries a bvhcache for the cache bvhtree of the request type. - */ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data, struct BMEditMesh *em, const int tree_type, @@ -1684,7 +1663,7 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data, mesh_eval_mutex); } else { - /* Setup BVHTreeFromMesh */ + /* Setup #BVHTreeFromMesh */ data->nearest_callback = nullptr; /* TODO */ data->raycast_callback = nullptr; /* TODO */ } @@ -1704,7 +1683,7 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data, mesh_eval_mutex); } else { - /* Setup BVHTreeFromMesh */ + /* Setup #BVHTreeFromMesh */ data->nearest_callback = editmesh_looptri_nearest_point; data->raycast_callback = editmesh_looptri_spherecast; } @@ -1741,7 +1720,10 @@ BVHTree *BKE_bvhtree_from_editmesh_get(BVHTreeFromEditMesh *data, /** \} */ -/* Frees data allocated by a call to bvhtree_from_editmesh_*. */ +/* -------------------------------------------------------------------- */ +/** \name Free Functions + * \{ */ + void free_bvhtree_from_editmesh(struct BVHTreeFromEditMesh *data) { if (data->tree) { @@ -1752,7 +1734,6 @@ void free_bvhtree_from_editmesh(struct BVHTreeFromEditMesh *data) } } -/* Frees data allocated by a call to bvhtree_from_mesh_*. */ void free_bvhtree_from_mesh(struct BVHTreeFromMesh *data) { if (data->tree && !data->cached) { diff --git a/source/blender/blenkernel/intern/cachefile.c b/source/blender/blenkernel/intern/cachefile.c index e642bbc9e06..75df2e98fcd 100644 --- a/source/blender/blenkernel/intern/cachefile.c +++ b/source/blender/blenkernel/intern/cachefile.c @@ -40,6 +40,7 @@ #include "BLT_translation.h" #include "BKE_anim_data.h" +#include "BKE_bpath.h" #include "BKE_cachefile.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" @@ -53,6 +54,8 @@ #include "BLO_read_write.h" +#include "MEM_guardedalloc.h" + #ifdef WITH_ALEMBIC # include "ABC_alembic.h" #endif @@ -85,6 +88,7 @@ static void cache_file_copy_data(Main *UNUSED(bmain), cache_file_dst->handle = NULL; cache_file_dst->handle_readers = NULL; BLI_duplicatelist(&cache_file_dst->object_paths, &cache_file_src->object_paths); + BLI_duplicatelist(&cache_file_dst->layers, &cache_file_src->layers); } static void cache_file_free_data(ID *id) @@ -92,6 +96,13 @@ static void cache_file_free_data(ID *id) CacheFile *cache_file = (CacheFile *)id; cachefile_handle_free(cache_file); BLI_freelistN(&cache_file->object_paths); + BLI_freelistN(&cache_file->layers); +} + +static void cache_file_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + CacheFile *cache_file = (CacheFile *)id; + BKE_bpath_foreach_path_fixed_process(bpath_data, cache_file->filepath); } static void cache_file_blend_write(BlendWriter *writer, ID *id, const void *id_address) @@ -110,6 +121,11 @@ static void cache_file_blend_write(BlendWriter *writer, ID *id, const void *id_a if (cache_file->adt) { BKE_animdata_blend_write(writer, cache_file->adt); } + + /* write layers */ + LISTBASE_FOREACH (CacheFileLayer *, layer, &cache_file->layers) { + BLO_write_struct(writer, CacheFileLayer, layer); + } } static void cache_file_blend_read_data(BlendDataReader *reader, ID *id) @@ -123,6 +139,9 @@ static void cache_file_blend_read_data(BlendDataReader *reader, ID *id) /* relink animdata */ BLO_read_data_address(reader, &cache_file->adt); BKE_animdata_blend_read_data(reader, cache_file->adt); + + /* relink layers */ + BLO_read_list(reader, &cache_file->layers); } IDTypeInfo IDType_ID_CF = { @@ -134,6 +153,7 @@ IDTypeInfo IDType_ID_CF = { .name_plural = "cache_files", .translation_context = BLT_I18NCONTEXT_ID_CACHEFILE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = cache_file_init_data, .copy_data = cache_file_copy_data, @@ -141,6 +161,7 @@ IDTypeInfo IDType_ID_CF = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = cache_file_foreach_path, .owner_get = NULL, .blend_write = cache_file_blend_write, @@ -355,7 +376,8 @@ void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file #ifdef WITH_ALEMBIC 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); + cache_file->handle = ABC_create_handle( + bmain, filepath, cache_file->layers.first, &cache_file->object_paths); BLI_strncpy(cache_file->handle_filepath, filepath, FILE_MAX); } #endif @@ -411,12 +433,6 @@ float BKE_cachefile_time_offset(const CacheFile *cache_file, const float time, c 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) @@ -432,3 +448,35 @@ bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file, const bool is_final_render = (eEvaluationMode)dag_eval_mode == DAG_EVAL_RENDER; return cache_file->use_render_procedural && !is_final_render; } + +CacheFileLayer *BKE_cachefile_add_layer(CacheFile *cache_file, const char filename[1024]) +{ + for (CacheFileLayer *layer = cache_file->layers.first; layer; layer = layer->next) { + if (STREQ(layer->filepath, filename)) { + return NULL; + } + } + + const int num_layers = BLI_listbase_count(&cache_file->layers); + + CacheFileLayer *layer = MEM_callocN(sizeof(CacheFileLayer), "CacheFileLayer"); + BLI_strncpy(layer->filepath, filename, sizeof(layer->filepath)); + + BLI_addtail(&cache_file->layers, layer); + + cache_file->active_layer = (char)(num_layers + 1); + + return layer; +} + +CacheFileLayer *BKE_cachefile_get_active_layer(CacheFile *cache_file) +{ + return BLI_findlink(&cache_file->layers, cache_file->active_layer - 1); +} + +void BKE_cachefile_remove_layer(CacheFile *cache_file, CacheFileLayer *layer) +{ + cache_file->active_layer = 0; + BLI_remlink(&cache_file->layers, layer); + MEM_freeN(layer); +} diff --git a/source/blender/blenkernel/intern/callbacks.c b/source/blender/blenkernel/intern/callbacks.c index 87d5961b12e..992eb896d2d 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,14 +84,27 @@ 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); } @@ -91,10 +112,9 @@ void BKE_callback_remove(bCallbackFuncStore *funcstore, eCbEvent evt) void BKE_callback_global_init(void) { - /* do nothing */ + callbacks_initialized = true; } -/* call on application exit */ void BKE_callback_global_finalize(void) { eCbEvent evt; @@ -107,4 +127,6 @@ void BKE_callback_global_finalize(void) 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 ed1f6fcb40a..7940936b64a 100644 --- a/source/blender/blenkernel/intern/camera.c +++ b/source/blender/blenkernel/intern/camera.c @@ -103,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); } } } @@ -140,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; } } @@ -183,6 +182,7 @@ IDTypeInfo IDType_ID_CA = { .name_plural = "cameras", .translation_context = BLT_I18NCONTEXT_ID_CAMERA, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = camera_init_data, .copy_data = camera_copy_data, @@ -190,6 +190,7 @@ IDTypeInfo IDType_ID_CA = { .make_local = NULL, .foreach_id = camera_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = camera_blend_write, @@ -217,7 +218,6 @@ void *BKE_camera_add(Main *bmain, const char *name) return cam; } -/* get the camera's dof value, takes the dof object into account */ float BKE_camera_object_dof_distance(Object *ob) { Camera *cam = (Camera *)ob->data; @@ -426,7 +426,6 @@ void BKE_camera_params_compute_viewplane( params->viewplane = viewplane; } -/* viewplane is assumed to be already computed */ void BKE_camera_params_compute_matrix(CameraParams *params) { rctf viewplane = params->viewplane; @@ -757,8 +756,6 @@ static bool camera_frame_fit_calc_from_data(CameraParams *params, return false; } -/* don't move the camera, just yield the fit location */ -/* r_scale only valid/useful for ortho cameras */ bool BKE_camera_view_frame_fit_to_scene( Depsgraph *depsgraph, const Scene *scene, Object *camera_ob, float r_co[3], float *r_scale) { @@ -909,7 +906,6 @@ static void camera_stereo3d_model_matrix(const Object *camera, } } -/* the view matrix is used by the viewport drawing, it is basically the inverted model matrix */ void BKE_camera_multiview_view_matrix(const RenderData *rd, const Object *camera, const bool is_left, @@ -1032,7 +1028,6 @@ static Object *camera_multiview_advanced(const Scene *scene, Object *camera, con return camera; } -/* returns the camera to be used for render */ Object *BKE_camera_multiview_render(const Scene *scene, Object *camera, const char *viewname) { const bool is_multiview = (camera != NULL) && (scene->r.scemode & R_MULTIVIEW) != 0; @@ -1128,7 +1123,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 039a971fe2c..a4f3e84a2bf 100644 --- a/source/blender/blenkernel/intern/cdderivedmesh.c +++ b/source/blender/blenkernel/intern/cdderivedmesh.c @@ -56,6 +56,7 @@ typedef struct { /* these point to data in the DerivedMesh custom data layers, * they are only here for efficiency and convenience */ MVert *mvert; + const float (*vert_normals)[3]; MEdge *medge; MFace *mface; MLoop *mloop; @@ -103,24 +104,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; @@ -161,7 +144,7 @@ static void cdDM_getVertCo(DerivedMesh *dm, int index, float r_co[3]) static void cdDM_getVertNo(DerivedMesh *dm, int index, float r_no[3]) { CDDerivedMesh *cddm = (CDDerivedMesh *)dm; - normal_short_to_float_v3(r_no, cddm->mvert[index].no); + copy_v3_v3(r_no, cddm->vert_normals[index]); } static const MeshElemMap *cdDM_getPolyMap(Object *ob, DerivedMesh *dm) @@ -231,10 +214,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; @@ -303,6 +282,7 @@ static DerivedMesh *cdDM_from_mesh_ex(Mesh *mesh, CustomData_merge(&mesh->pdata, &dm->polyData, cddata_masks.pmask, alloctype, mesh->totpoly); cddm->mvert = CustomData_get_layer(&dm->vertData, CD_MVERT); + cddm->vert_normals = CustomData_get_layer(&dm->vertData, CD_NORMAL); cddm->medge = CustomData_get_layer(&dm->edgeData, CD_MEDGE); cddm->mloop = CustomData_get_layer(&dm->loopData, CD_MLOOP); cddm->mpoly = CustomData_get_layer(&dm->polyData, CD_MPOLY); diff --git a/source/blender/blenkernel/intern/cloth.c b/source/blender/blenkernel/intern/cloth.c index 080a7c90c46..43b8690e219 100644 --- a/source/blender/blenkernel/intern/cloth.c +++ b/source/blender/blenkernel/intern/cloth.c @@ -326,6 +326,7 @@ static int do_step_cloth( /************************************************ * clothModifier_do - main simulation function ************************************************/ + void clothModifier_do(ClothModifierData *clmd, Depsgraph *depsgraph, Scene *scene, @@ -433,7 +434,6 @@ void clothModifier_do(ClothModifierData *clmd, clmd->clothObject->last_frame = framenr; } -/* frees all */ void cloth_free_modifier(ClothModifierData *clmd) { Cloth *cloth = NULL; @@ -504,7 +504,6 @@ void cloth_free_modifier(ClothModifierData *clmd) } } -/* frees all */ void cloth_free_modifier_extern(ClothModifierData *clmd) { Cloth *cloth = NULL; @@ -1401,8 +1400,7 @@ static bool find_internal_spring_target_vertex(BVHTreeFromMesh *treedata, float radius; copy_v3_v3(co, treedata->vert[v_idx].co); - normal_short_to_float_v3(no, treedata->vert[v_idx].no); - negate_v3(no); + negate_v3_v3(no, treedata->vert_normals[v_idx]); float vec_len = sin(max_diversion); float offset[3]; diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index 2d172f23428..e6ce4eb9440 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -158,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 @@ -170,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); } } @@ -185,7 +186,7 @@ static ID *collection_owner_get(Main *bmain, ID *id) Collection *master_collection = (Collection *)id; BLI_assert((master_collection->flag & COLLECTION_IS_MASTER) != 0); - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { if (scene->master_collection == master_collection) { return &scene->id; } @@ -374,6 +375,7 @@ IDTypeInfo IDType_ID_GR = { .name_plural = "collections", .translation_context = BLT_I18NCONTEXT_ID_COLLECTION, .flags = IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = collection_init_data, .copy_data = collection_copy_data, @@ -381,6 +383,7 @@ IDTypeInfo IDType_ID_GR = { .make_local = NULL, .foreach_id = collection_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = collection_owner_get, .blend_write = collection_blend_write, @@ -428,10 +431,6 @@ static Collection *collection_add(Main *bmain, return collection; } -/** - * Add a collection to a collection ListBase and synchronize all render layers - * The ListBase is NULL when the collection is to be added to the master collection - */ Collection *BKE_collection_add(Main *bmain, Collection *collection_parent, const char *name_custom) { Collection *collection = collection_add(bmain, collection_parent, name_custom); @@ -439,12 +438,6 @@ Collection *BKE_collection_add(Main *bmain, Collection *collection_parent, const return collection; } -/** - * Add \a collection_dst to all scene collections that reference object \a ob_src is in. - * Used to replace an instance object with a collection (library override operator). - * - * Logic is very similar to #BKE_collection_object_add_from(). - */ void BKE_collection_add_from_object(Main *bmain, Scene *scene, const Object *ob_src, @@ -467,12 +460,6 @@ void BKE_collection_add_from_object(Main *bmain, BKE_main_collection_sync(bmain); } -/** - * Add \a collection_dst to all scene collections that reference collection \a collection_src is - * in. - * - * Logic is very similar to #BKE_collection_object_add_from(). - */ void BKE_collection_add_from_collection(Main *bmain, Scene *scene, Collection *collection_src, @@ -506,17 +493,12 @@ void BKE_collection_add_from_collection(Main *bmain, /** \name Free and Delete Collection * \{ */ -/** Free (or release) any data used by this collection (does not free the collection itself). */ void BKE_collection_free_data(Collection *collection) { BKE_libblock_free_data(&collection->id, false); collection_free_data(&collection->id); } -/** - * Remove a collection, optionally removing its child objects or moving - * them to parent collections. - */ bool BKE_collection_delete(Main *bmain, Collection *collection, bool hierarchy) { /* Master collection is not real datablock, can't be removed. */ @@ -597,7 +579,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; @@ -677,14 +659,6 @@ static Collection *collection_duplicate_recursive(Main *bmain, return collection_new; } -/** - * Make a deep copy (aka duplicate) of the given collection and all of its children, recursively. - * - * \warning This functions will clear all \a bmain #ID.idnew pointers, unless \a - * #LIB_ID_DUPLICATE_IS_SUBPROCESS duplicate option is passed on, in which case caller is - * responsible to reconstruct collection dependencies information's - * (i.e. call #BKE_main_collection_sync). - */ Collection *BKE_collection_duplicate(Main *bmain, Collection *parent, Collection *collection, @@ -715,7 +689,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. */ @@ -743,9 +717,6 @@ Collection *BKE_collection_duplicate(Main *bmain, /** \name Collection Naming * \{ */ -/** - * The automatic/fallback name of a new collection. - */ void BKE_collection_new_name_get(Collection *collection_parent, char *rname) { char *name; @@ -768,9 +739,6 @@ void BKE_collection_new_name_get(Collection *collection_parent, char *rname) MEM_freeN(name); } -/** - * The name to show in the interface. - */ const char *BKE_collection_ui_name_get(struct Collection *collection) { if (collection->flag & COLLECTION_IS_MASTER) { @@ -1126,9 +1094,6 @@ static bool collection_object_remove(Main *bmain, return true; } -/** - * Add object to collection - */ bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob) { if (ELEM(NULL, collection, ob)) { @@ -1157,12 +1122,6 @@ bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob) return true; } -/** - * Add \a ob_dst to all scene collections that reference object \a ob_src is in. - * Used for copying objects. - * - * Logic is very similar to #BKE_collection_add_from_object() - */ void BKE_collection_object_add_from(Main *bmain, Scene *scene, Object *ob_src, Object *ob_dst) { bool is_instantiated = false; @@ -1185,9 +1144,6 @@ void BKE_collection_object_add_from(Main *bmain, Scene *scene, Object *ob_src, O BKE_main_collection_sync(bmain); } -/** - * Remove object from collection. - */ bool BKE_collection_object_remove(Main *bmain, Collection *collection, Object *ob, @@ -1235,9 +1191,6 @@ static bool scene_collections_object_remove( return removed; } -/** - * Remove object from all collections of scene - */ bool BKE_scene_collections_object_remove(Main *bmain, Scene *scene, Object *ob, const bool free_us) { return scene_collections_object_remove(bmain, scene, ob, free_us, NULL); @@ -1252,9 +1205,7 @@ static void collection_object_remove_nulls(Collection *collection) { bool changed = false; - for (CollectionObject *cob = collection->gobject.first, *cob_next = NULL; cob; cob = cob_next) { - cob_next = cob->next; - + LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) { if (cob->ob == NULL) { BLI_freelinkN(&collection->gobject, cob); changed = true; @@ -1268,22 +1219,61 @@ static void collection_object_remove_nulls(Collection *collection) void BKE_collections_object_remove_nulls(Main *bmain) { - for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { collection_object_remove_nulls(scene->master_collection); } - for (Collection *collection = bmain->collections.first; collection; - collection = collection->id.next) { + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { collection_object_remove_nulls(collection); } } -static void collection_null_children_remove(Collection *collection) +/* + * Remove all duplicate objects from collections. + * This is used for library remapping, happens when remapping an object to another one already + * present in the collection. Otherwise this should never happen. + */ +static void collection_object_remove_duplicates(Collection *collection) +{ + bool changed = false; + + LISTBASE_FOREACH_MUTABLE (CollectionObject *, cob, &collection->gobject) { + if (cob->ob->runtime.collection_management) { + BLI_freelinkN(&collection->gobject, cob); + changed = true; + continue; + } + cob->ob->runtime.collection_management = true; + } + + /* Cleanup. */ + LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) { + cob->ob->runtime.collection_management = false; + } + + if (changed) { + BKE_collection_object_cache_free(collection); + } +} + +void BKE_collections_object_remove_duplicates(struct Main *bmain) { - for (CollectionChild *child = collection->children.first, *child_next = NULL; child; - child = child_next) { - child_next = child->next; + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + ob->runtime.collection_management = false; + } + + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + collection_object_remove_duplicates(scene->master_collection); + } + + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + collection_object_remove_duplicates(collection); + } +} +static void collection_null_children_remove(Collection *collection) +{ + LISTBASE_FOREACH_MUTABLE (CollectionChild *, child, &collection->children) { if (child->collection == NULL) { BLI_freelinkN(&collection->children, child); } @@ -1292,27 +1282,13 @@ static void collection_null_children_remove(Collection *collection) static void collection_missing_parents_remove(Collection *collection) { - for (CollectionParent *parent = collection->parents.first, *parent_next; parent != NULL; - parent = parent_next) { - parent_next = parent->next; + LISTBASE_FOREACH_MUTABLE (CollectionParent *, parent, &collection->parents) { if ((parent->collection == NULL) || !collection_find_child(parent->collection, collection)) { BLI_freelinkN(&collection->parents, parent); } } } -/** - * Remove all NULL children from parent collections of changed \a collection. - * This is used for library remapping, where these pointers have been set to NULL. - * Otherwise this should never happen. - * - * \note caller must ensure #BKE_main_collection_sync_remap() is called afterwards! - * - * \param parent_collection: The collection owning the pointers that were remapped. May be \a NULL, - * in which case whole \a bmain database of collections is checked. - * \param child_collection: The collection that was remapped to another pointer. May be \a NULL, - * in which case whole \a bmain database of collections is checked. - */ void BKE_collections_child_remove_nulls(Main *bmain, Collection *parent_collection, Collection *child_collection) @@ -1326,28 +1302,23 @@ void BKE_collections_child_remove_nulls(Main *bmain, * otherwise we can miss some cases... * Also, master collections are not in bmain, so we also need to loop over scenes. */ - for (child_collection = bmain->collections.first; child_collection != NULL; - child_collection = child_collection->id.next) { - collection_null_children_remove(child_collection); + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + collection_null_children_remove(collection); } - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { collection_null_children_remove(scene->master_collection); } } - for (child_collection = bmain->collections.first; child_collection != NULL; - child_collection = child_collection->id.next) { - collection_missing_parents_remove(child_collection); + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + collection_missing_parents_remove(collection); } - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { collection_missing_parents_remove(scene->master_collection); } } else { - for (CollectionParent *parent = child_collection->parents.first, *parent_next; parent; - parent = parent_next) { - parent_next = parent->next; - + LISTBASE_FOREACH_MUTABLE (CollectionParent *, parent, &child_collection->parents) { collection_null_children_remove(parent->collection); if (!collection_find_child(parent->collection, child_collection)) { @@ -1357,11 +1328,6 @@ void BKE_collections_child_remove_nulls(Main *bmain, } } -/** - * Move object from a collection into another - * - * If source collection is NULL move it from all the existing collections. - */ void BKE_collection_object_move( Main *bmain, Scene *scene, Collection *collection_dst, Collection *collection_src, Object *ob) { @@ -1436,15 +1402,6 @@ static bool collection_instance_find_recursive(Collection *collection, return false; } -/** - * Find potential cycles in collections. - * - * \param new_ancestor: the potential new owner of given \a collection, - * or the collection to check if the later is NULL. - * \param collection: the collection we want to add to \a new_ancestor, - * may be NULL if we just want to ensure \a new_ancestor does not already have cycles. - * \return true if a cycle is found. - */ bool BKE_collection_cycle_find(Collection *new_ancestor, Collection *collection) { if (collection == new_ancestor) { @@ -1508,12 +1465,6 @@ static bool collection_cycle_fix_recursive(Main *bmain, return cycles_found; } -/** - * Find and fix potential cycles in collections. - * - * \param collection: The collection to check for existing cycles. - * \return true if cycles are found and fixed. - */ bool BKE_collection_cycles_fix(Main *bmain, Collection *collection) { return collection_cycle_fix_recursive(bmain, collection, collection) || @@ -1626,11 +1577,6 @@ bool BKE_collection_child_remove(Main *bmain, Collection *parent, Collection *ch return true; } -/** - * Rebuild parent relationships from child ones, for all children of given \a collection. - * - * \note Given collection is assumed to already have valid parents. - */ void BKE_collection_parent_relations_rebuild(Collection *collection) { LISTBASE_FOREACH_MUTABLE (CollectionChild *, child, &collection->children) { @@ -1670,23 +1616,19 @@ static void collection_parents_rebuild_recursive(Collection *collection) BKE_collection_parent_relations_rebuild(collection); collection->tag &= ~COLLECTION_TAG_RELATION_REBUILD; - for (CollectionChild *child = collection->children.first; child != NULL; child = child->next) { + LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { /* See comment above in `BKE_collection_parent_relations_rebuild`. */ - if ((collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) { + if ((child->collection->id.tag & (LIB_TAG_NO_MAIN | LIB_TAG_COPIED_ON_WRITE)) != 0) { continue; } collection_parents_rebuild_recursive(child->collection); } } -/** - * Rebuild parent relationships from child ones, for all collections in given \a bmain. - */ void BKE_main_collections_parent_relations_rebuild(Main *bmain) { /* Only collections not in bmain (master ones in scenes) have no parent... */ - for (Collection *collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { BLI_freelistN(&collection->parents); collection->tag |= COLLECTION_TAG_RELATION_REBUILD; @@ -1694,7 +1636,7 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain) /* Scene's master collections will be 'root' parent of most of our collections, so start with * them. */ - for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { /* This function can be called from readfile.c, when this pointer is not guaranteed to be NULL. */ if (scene->master_collection != NULL) { @@ -1706,8 +1648,7 @@ void BKE_main_collections_parent_relations_rebuild(Main *bmain) /* We may have parent chains outside of scene's master_collection context? At least, readfile's * lib_link_collection_data() seems to assume that, so do the same here. */ - for (Collection *collection = bmain->collections.first; collection != NULL; - collection = collection->id.next) { + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { if (collection->tag & COLLECTION_TAG_RELATION_REBUILD) { /* 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 @@ -1742,11 +1683,6 @@ static Collection *collection_from_index_recursive(Collection *collection, return NULL; } -/** - * Return Scene Collection for a given index. - * - * The index is calculated from top to bottom counting the children before the siblings. - */ Collection *BKE_collection_from_index(Scene *scene, const int index) { int index_current = 0; @@ -1790,10 +1726,6 @@ static bool collection_objects_select(ViewLayer *view_layer, Collection *collect return changed; } -/** - * Select all the objects in this Collection (and its nested collections) for this ViewLayer. - * Return true if any object was selected. - */ bool BKE_collection_objects_select(ViewLayer *view_layer, Collection *collection, bool deselect) { LayerCollection *layer_collection = BKE_layer_collection_first_from_scene_collection(view_layer, @@ -1919,10 +1851,6 @@ static void scene_collections_array(Scene *scene, scene_collection_callback(collection, scene_collections_build_array, &array); } -/** - * Only use this in non-performance critical situations - * (it iterates over all scene collections twice) - */ void BKE_scene_collections_iterator_begin(BLI_Iterator *iter, void *data_in) { Scene *scene = data_in; @@ -2064,13 +1992,6 @@ 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.). - */ GSet *BKE_scene_objects_as_gset(Scene *scene, GSet *objects_gset) { BLI_Iterator iter; diff --git a/source/blender/blenkernel/intern/collision.c b/source/blender/blenkernel/intern/collision.c index 1c24dae430c..671c6530685 100644 --- a/source/blender/blenkernel/intern/collision.c +++ b/source/blender/blenkernel/intern/collision.c @@ -78,7 +78,6 @@ typedef struct SelfColDetectData { * Collision modifier code start ***********************************/ -/* step is limited from 0 (frame start position) to 1 (frame end position) */ void collision_move_object(CollisionModifierData *collmd, const float step, const float prevstep, @@ -577,7 +576,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], @@ -1261,9 +1260,6 @@ static void add_collision_object(ListBase *relations, } } -/* Create list of collision relations in the collection or entire scene. - * This is used by the depsgraph to build relations, as well as faster - * lookup of colliders during evaluation. */ ListBase *BKE_collision_relations_create(Depsgraph *depsgraph, Collection *collection, unsigned int modifier_type) @@ -1292,8 +1288,6 @@ void BKE_collision_relations_free(ListBase *relations) } } -/* Create effective list of colliders from relations built beforehand. - * Self will be excluded. */ Object **BKE_collision_objects_create(Depsgraph *depsgraph, Object *self, Collection *collection, @@ -1341,8 +1335,6 @@ void BKE_collision_objects_free(Object **objects) } } -/* Create effective list of colliders from relations built beforehand. - * Self will be excluded. */ ListBase *BKE_collider_cache_create(Depsgraph *depsgraph, Object *self, Collection *collection) { ListBase *relations = DEG_get_collision_relations( diff --git a/source/blender/blenkernel/intern/colortools.c b/source/blender/blenkernel/intern/colortools.c index f2c2e552a9f..b12b19453ae 100644 --- a/source/blender/blenkernel/intern/colortools.c +++ b/source/blender/blenkernel/intern/colortools.c @@ -183,7 +183,6 @@ void BKE_curvemapping_set_black_white(CurveMapping *cumap, /* ***************** operations on single curve ************* */ /* ********** NOTE: requires BKE_curvemapping_changed() call after ******** */ -/* remove specified point */ bool BKE_curvemap_remove_point(CurveMap *cuma, CurveMapPoint *point) { CurveMapPoint *cmp; @@ -213,7 +212,6 @@ bool BKE_curvemap_remove_point(CurveMap *cuma, CurveMapPoint *point) return (removed != 0); } -/* removes with flag set */ void BKE_curvemap_remove(CurveMap *cuma, const short flag) { CurveMapPoint *cmp = MEM_mallocN((cuma->totpoint) * sizeof(CurveMapPoint), "curve points"); @@ -439,9 +437,6 @@ void BKE_curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope } } -/** - * \param type: eBezTriple_Handle - */ void BKE_curvemap_handle_set(CurveMap *cuma, int type) { int a; @@ -800,10 +795,10 @@ static void curvemap_make_table(const CurveMapping *cumap, CurveMap *cuma) cuma->table = cmp; } -/* call when you do images etc, needs restore too. also verifies tables */ -/* it uses a flag to prevent premul or free to happen twice */ -void BKE_curvemapping_premultiply(CurveMapping *cumap, int restore) +void BKE_curvemapping_premultiply(CurveMapping *cumap, bool restore) { + /* It uses a flag to prevent pre-multiply or free to happen twice. */ + int a; if (restore) { @@ -873,7 +868,6 @@ static int sort_curvepoints(const void *a1, const void *a2) /* ************************ more CurveMapping calls *************** */ -/* NOTE: only does current curvemap! */ void BKE_curvemapping_changed(CurveMapping *cumap, const bool rem_doubles) { CurveMap *cuma = cumap->cm + cumap->cur; @@ -965,13 +959,11 @@ void BKE_curvemapping_changed_all(CurveMapping *cumap) cumap->cur = cur; } -/* Reset the view for current curve. */ void BKE_curvemapping_reset_view(CurveMapping *cumap) { cumap->curr = cumap->clipr; } -/* table should be verified */ float BKE_curvemap_evaluateF(const CurveMapping *cumap, const CurveMap *cuma, float value) { /* index in table */ @@ -994,7 +986,6 @@ float BKE_curvemap_evaluateF(const CurveMapping *cumap, const CurveMap *cuma, fl return (1.0f - fi) * cuma->table[i].y + (fi)*cuma->table[i + 1].y; } -/* works with curve 'cur' */ float BKE_curvemapping_evaluateF(const CurveMapping *cumap, int cur, float value) { const CurveMap *cuma = cumap->cm + cur; @@ -1013,7 +1004,6 @@ float BKE_curvemapping_evaluateF(const CurveMapping *cumap, int cur, float value return val; } -/* vector case */ void BKE_curvemapping_evaluate3F(const CurveMapping *cumap, float vecout[3], const float vecin[3]) { vecout[0] = BKE_curvemap_evaluateF(cumap, &cumap->cm[0], vecin[0]); @@ -1021,7 +1011,6 @@ void BKE_curvemapping_evaluate3F(const CurveMapping *cumap, float vecout[3], con vecout[2] = BKE_curvemap_evaluateF(cumap, &cumap->cm[2], vecin[2]); } -/* RGB case, no black/white points, no premult */ void BKE_curvemapping_evaluateRGBF(const CurveMapping *cumap, float vecout[3], const float vecin[3]) @@ -1052,16 +1041,6 @@ static void curvemapping_evaluateRGBF_filmlike(const CurveMapping *cumap, vecout[channel_offset[2]] = v2; } -/** - * Same as #BKE_curvemapping_evaluate_premulRGBF - * but black/bwmul are passed as args for the compositor - * where they can change per pixel. - * - * Use in conjunction with #BKE_curvemapping_set_black_white_ex - * - * \param black: Use instead of cumap->black - * \param bwmul: Use instead of cumap->bwmul - */ void BKE_curvemapping_evaluate_premulRGBF_ex(const CurveMapping *cumap, float vecout[3], const float vecin[3], @@ -1127,7 +1106,6 @@ void BKE_curvemapping_evaluate_premulRGBF_ex(const CurveMapping *cumap, } } -/* RGB with black/white points and premult. tables are checked */ void BKE_curvemapping_evaluate_premulRGBF(const CurveMapping *cumap, float vecout[3], const float vecin[3]) @@ -1135,7 +1113,6 @@ void BKE_curvemapping_evaluate_premulRGBF(const CurveMapping *cumap, BKE_curvemapping_evaluate_premulRGBF_ex(cumap, vecout, vecin, cumap->black, cumap->bwmul); } -/* same as above, byte version */ void BKE_curvemapping_evaluate_premulRGB(const CurveMapping *cumap, unsigned char vecout_byte[3], const unsigned char vecin_byte[3]) @@ -1212,6 +1189,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; @@ -1248,7 +1239,6 @@ void BKE_curvemapping_curves_blend_write(BlendWriter *writer, const CurveMapping } } -/* cumap itself has been read already. */ void BKE_curvemapping_blend_read(BlendDataReader *reader, CurveMapping *cumap) { /* flag seems to be able to hang? Maybe old files... not bad to clear anyway */ diff --git a/source/blender/blenkernel/intern/constraint.c b/source/blender/blenkernel/intern/constraint.c index b2b03d28483..f013ef99dde 100644 --- a/source/blender/blenkernel/intern/constraint.c +++ b/source/blender/blenkernel/intern/constraint.c @@ -34,6 +34,7 @@ #include "BLI_blenlib.h" #include "BLI_kdopbvh.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string_utils.h" #include "BLI_utildefines.h" @@ -71,6 +72,7 @@ #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_lib_id.h" +#include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_movieclip.h" #include "BKE_object.h" @@ -123,7 +125,6 @@ static bConstraint *constraint_find_original_for_update(bConstraintOb *cob, bCon /* -------------- Naming -------------- */ -/* Find the first available, non-duplicate name for a given constraint */ void BKE_constraint_unique_name(bConstraint *con, ListBase *list) { BLI_uniquename(list, con, DATA_("Const"), '.', offsetof(bConstraint, name), sizeof(con->name)); @@ -132,8 +133,6 @@ void BKE_constraint_unique_name(bConstraint *con, ListBase *list) /* ----------------- Evaluation Loop Preparation --------------- */ /* package an object/bone for use in constraint evaluation */ -/* This function MEM_calloc's a bConstraintOb struct, - * that will need to be freed after evaluation */ bConstraintOb *BKE_constraints_make_evalob( Depsgraph *depsgraph, Scene *scene, Object *ob, void *subdata, short datatype) { @@ -211,7 +210,6 @@ bConstraintOb *BKE_constraints_make_evalob( return cob; } -/* cleanup after constraint evaluation */ void BKE_constraints_clear_evalob(bConstraintOb *cob) { float delta[4][4], imat[4][4]; @@ -261,10 +259,6 @@ void BKE_constraints_clear_evalob(bConstraintOb *cob) /* -------------- Space-Conversion API -------------- */ -/* This function is responsible for the correct transformations/conversions - * of a matrix from one space to another for constraint evaluation. - * For now, this is only implemented for Objects and PoseChannels. - */ void BKE_constraint_mat_convertspace(Object *ob, bPoseChannel *pchan, bConstraintOb *cob, @@ -551,6 +545,7 @@ static void contarget_get_mesh_mat(Object *ob, const char *substring, float mat[ float vec[3] = {0.0f, 0.0f, 0.0f}; float normal[3] = {0.0f, 0.0f, 0.0f}; float weightsum = 0.0f; + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me_eval); if (me_eval) { const MDeformVert *dvert = CustomData_get_layer(&me_eval->vdata, CD_MDEFORMVERT); int numVerts = me_eval->totvert; @@ -565,10 +560,8 @@ static void contarget_get_mesh_mat(Object *ob, const char *substring, float mat[ const MDeformWeight *dw = BKE_defvert_find_index(dv, defgroup); if (dw && dw->weight > 0.0f) { - float nor[3]; - normal_short_to_float_v3(nor, mv->no); madd_v3_v3fl(vec, mv->co, dw->weight); - madd_v3_v3fl(normal, nor, dw->weight); + madd_v3_v3fl(normal, vert_normals[i], dw->weight); weightsum += dw->weight; } } @@ -2010,7 +2003,7 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar /* We must get compatible eulers from the beginning because * some of them can be modified below (see bug T21875). * Additionally, since this constraint is based on euler rotation math, it doesn't work well - * with shear. The Y axis is chosen as the main axis when we orthoganalize the matrix because + * with shear. The Y axis is chosen as the main axis when we orthogonalize the matrix because * constraints are used most commonly on bones. */ float mat[4][4]; copy_m4_m4(mat, ct->matrix); @@ -3763,7 +3756,7 @@ static void minmax_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targ copy_m4_m4(tarmat, ct->matrix); if (data->flag & MINMAX_USEROT) { - /* take rotation of target into account by doing the transaction in target's localspace */ + /* Take rotation of target into account by doing the transaction in target's local-space. */ invert_m4_m4(imat, tarmat); mul_m4_m4m4(tmat, imat, obmat); copy_m4_m4(obmat, tmat); @@ -3808,7 +3801,7 @@ static void minmax_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targ if (val1 > val2) { obmat[3][index] = tarmat[3][index] + data->offset; if (data->flag & MINMAX_USEROT) { - /* get out of localspace */ + /* Get out of local-space. */ mul_m4_m4m4(tmat, ct->matrix, obmat); copy_m4_m4(cob->matrix, tmat); } @@ -5556,9 +5549,6 @@ static void constraints_init_typeinfo(void) constraintsTypeInfo[30] = &CTI_ARMATURE; /* Armature Constraint */ } -/* This function should be used for getting the appropriate type-info when only - * a constraint type is known - */ const bConstraintTypeInfo *BKE_constraint_typeinfo_from_type(int type) { /* initialize the type-info list? */ @@ -5578,9 +5568,6 @@ const bConstraintTypeInfo *BKE_constraint_typeinfo_from_type(int type) return NULL; } -/* This function should always be used to get the appropriate type-info, as it - * has checks which prevent segfaults in some weird cases. - */ const bConstraintTypeInfo *BKE_constraint_typeinfo_get(bConstraint *con) { /* only return typeinfo for valid constraints */ @@ -5611,11 +5598,6 @@ static void con_unlink_refs_cb(bConstraint *UNUSED(con), } } -/** - * Free data of a specific constraint if it has any info. - * be sure to run #BIK_clear_data() when freeing an IK constraint, - * unless DAG_relations_tag_update is called. - */ void BKE_constraint_free_data_ex(bConstraint *con, bool do_id_user) { if (con->data) { @@ -5643,7 +5625,6 @@ void BKE_constraint_free_data(bConstraint *con) BKE_constraint_free_data_ex(con, true); } -/* Free all constraints from a constraint-stack */ void BKE_constraints_free_ex(ListBase *list, bool do_id_user) { /* Free constraint data and also any extra data */ @@ -5660,7 +5641,6 @@ void BKE_constraints_free(ListBase *list) BKE_constraints_free_ex(list, true); } -/* Remove the specified constraint from the given constraint stack */ bool BKE_constraint_remove(ListBase *list, bConstraint *con) { if (con) { @@ -5686,7 +5666,6 @@ 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, @@ -5698,13 +5677,19 @@ bool BKE_constraint_apply_for_object(Depsgraph *depsgraph, const float ctime = BKE_scene_frame_get(scene); - bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob)); + /* Do this all in the evaluated domain (e.g. shrinkwrap needs to access evaluated constraint + * target mesh). */ + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); + bConstraint *con_eval = BKE_constraints_find_name(&ob_eval->constraints, con->name); + + bConstraint *new_con = BKE_constraint_duplicate_ex(con_eval, 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); + depsgraph, scene_eval, ob_eval, NULL, CONSTRAINT_OBTYPE_OBJECT); /* Undo the effect of the current constraint stack evaluation. */ - mul_m4_m4m4(cob->matrix, ob->constinv, cob->matrix); + mul_m4_m4m4(cob->matrix, ob_eval->constinv, cob->matrix); /* Evaluate single constraint. */ BKE_constraints_solve(depsgraph, &single_con, cob, ctime); @@ -5717,7 +5702,7 @@ bool BKE_constraint_apply_for_object(Depsgraph *depsgraph, BLI_freelinkN(&single_con, new_con); /* Apply transform from matrix. */ - BKE_object_apply_mat4(ob, ob->obmat, true, true); + BKE_object_apply_mat4(ob, ob_eval->obmat, true, true); return true; } @@ -5744,18 +5729,25 @@ bool BKE_constraint_apply_for_pose( const float ctime = BKE_scene_frame_get(scene); - bConstraint *new_con = BKE_constraint_duplicate_ex(con, 0, !ID_IS_LINKED(ob)); + /* Do this all in the evaluated domain (e.g. shrinkwrap needs to access evaluated constraint + * target mesh). */ + Scene *scene_eval = DEG_get_evaluated_scene(depsgraph); + Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob); + bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); + bConstraint *con_eval = BKE_constraints_find_name(&pchan_eval->constraints, con->name); + + bConstraint *new_con = BKE_constraint_duplicate_ex(con_eval, 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]); + copy_v3_v3(vec, pchan_eval->pose_mat[3]); bConstraintOb *cob = BKE_constraints_make_evalob( - depsgraph, scene, ob, pchan, CONSTRAINT_OBTYPE_BONE); + depsgraph, scene_eval, ob_eval, pchan_eval, CONSTRAINT_OBTYPE_BONE); /* Undo the effects of currently applied constraints. */ - mul_m4_m4m4(cob->matrix, pchan->constinv, cob->matrix); + mul_m4_m4m4(cob->matrix, pchan_eval->constinv, cob->matrix); /* Evaluate single constraint. */ BKE_constraints_solve(depsgraph, &single_con, cob, ctime); BKE_constraints_clear_evalob(cob); @@ -5766,12 +5758,12 @@ bool BKE_constraint_apply_for_pose( /* Prevent constraints breaking a chain. */ if (pchan->bone->flag & BONE_CONNECTED) { - copy_v3_v3(pchan->pose_mat[3], vec); + copy_v3_v3(pchan_eval->pose_mat[3], vec); } /* Apply transform from matrix. */ float mat[4][4]; - BKE_armature_mat_pose_to_bone(pchan, pchan->pose_mat, mat); + BKE_armature_mat_pose_to_bone(pchan, pchan_eval->pose_mat, mat); BKE_pchan_apply_mat4(pchan, mat, true); return true; @@ -5920,7 +5912,6 @@ bool BKE_constraint_target_uses_bbone(struct bConstraint *con, /* ......... */ -/* Add new constraint for the given bone */ bConstraint *BKE_constraint_add_for_pose(Object *ob, bPoseChannel *pchan, const char *name, @@ -5933,7 +5924,6 @@ bConstraint *BKE_constraint_add_for_pose(Object *ob, return add_new_constraint(ob, pchan, name, type); } -/* Add new constraint for the given object */ bConstraint *BKE_constraint_add_for_object(Object *ob, const char *name, short type) { return add_new_constraint(ob, NULL, name, type); @@ -5941,7 +5931,6 @@ bConstraint *BKE_constraint_add_for_object(Object *ob, const char *name, short t /* ......... */ -/* Run the given callback on all ID-blocks in list of constraints */ void BKE_constraints_id_loop(ListBase *conlist, ConstraintIDFunc func, void *userdata) { LISTBASE_FOREACH (bConstraint *, con, conlist) { @@ -6016,17 +6005,14 @@ static void constraint_copy_data_ex(bConstraint *dst, } } -/** Allocate and duplicate a single constraint, outside of any object/pose context. */ bConstraint *BKE_constraint_duplicate_ex(bConstraint *src, const int flag, const bool do_extern) { bConstraint *dst = MEM_dupallocN(src); constraint_copy_data_ex(dst, src, flag, do_extern); dst->next = dst->prev = NULL; - dst->flag |= CONSTRAINT_OVERRIDE_LIBRARY_LOCAL; return dst; } -/* Add a copy of the given constraint for the given bone */ bConstraint *BKE_constraint_copy_for_pose(Object *ob, bPoseChannel *pchan, bConstraint *src) { if (pchan == NULL) { @@ -6038,7 +6024,6 @@ bConstraint *BKE_constraint_copy_for_pose(Object *ob, bPoseChannel *pchan, bCons return new_con; } -/* Add a copy of the given constraint for the given object */ bConstraint *BKE_constraint_copy_for_object(Object *ob, bConstraint *src) { bConstraint *new_con = BKE_constraint_duplicate_ex(src, 0, !ID_IS_LINKED(ob)); @@ -6046,7 +6031,6 @@ bConstraint *BKE_constraint_copy_for_object(Object *ob, bConstraint *src) return new_con; } -/* duplicate all of the constraints in a constraint stack */ void BKE_constraints_copy_ex(ListBase *dst, const ListBase *src, const int flag, bool do_extern) { bConstraint *con, *srccon; @@ -6057,7 +6041,9 @@ void BKE_constraints_copy_ex(ListBase *dst, const ListBase *src, const int flag, for (con = dst->first, srccon = src->first; con && srccon; srccon = srccon->next, con = con->next) { constraint_copy_data_ex(con, srccon, flag, do_extern); - con->flag |= CONSTRAINT_OVERRIDE_LIBRARY_LOCAL; + if ((flag & LIB_ID_COPY_NO_LIB_OVERRIDE_LOCAL_DATA_FLAG) == 0) { + con->flag |= CONSTRAINT_OVERRIDE_LIBRARY_LOCAL; + } } } @@ -6073,7 +6059,6 @@ bConstraint *BKE_constraints_find_name(ListBase *list, const char *name) return BLI_findstring(list, name, offsetof(bConstraint, name)); } -/* finds the 'active' constraint in a constraint stack */ bConstraint *BKE_constraints_active_get(ListBase *list) { @@ -6090,7 +6075,6 @@ bConstraint *BKE_constraints_active_get(ListBase *list) return NULL; } -/* Set the given constraint as the active one (clearing all the others) */ void BKE_constraints_active_set(ListBase *list, bConstraint *con) { @@ -6126,7 +6110,6 @@ static bConstraint *constraint_list_find_from_target(ListBase *constraints, bCon return NULL; } -/* Finds the constraint that owns the given target within the object. */ bConstraint *BKE_constraint_find_from_target(Object *ob, bConstraintTarget *tgt, bPoseChannel **r_pchan) @@ -6224,12 +6207,6 @@ static bConstraint *constraint_find_original_for_update(bConstraintOb *cob, bCon return orig_con; } -/** - * Check whether given constraint is not local (i.e. from linked data) when the object is a library - * override. - * - * \param con: May be NULL, in which case we consider it as a non-local constraint case. - */ bool BKE_constraint_is_nonlocal_in_liboverride(const Object *ob, const bConstraint *con) { return (ID_IS_OVERRIDE_LIBRARY(ob) && @@ -6238,8 +6215,6 @@ bool BKE_constraint_is_nonlocal_in_liboverride(const Object *ob, const bConstrai /* -------- Constraints and Proxies ------- */ -/* Rescue all constraints tagged as being CONSTRAINT_PROXY_LOCAL - * (i.e. added to bone that's proxy-synced in this file) */ void BKE_constraints_proxylocal_extract(ListBase *dst, ListBase *src) { bConstraint *con, *next; @@ -6256,7 +6231,6 @@ void BKE_constraints_proxylocal_extract(ListBase *dst, ListBase *src) } } -/* Returns if the owner of the constraint is proxy-protected */ bool BKE_constraints_proxylocked_owner(Object *ob, bPoseChannel *pchan) { /* Currently, constraints can only be on object or bone level */ @@ -6280,13 +6254,6 @@ bool BKE_constraints_proxylocked_owner(Object *ob, bPoseChannel *pchan) /* -------- Target-Matrix Stuff ------- */ -/* This function is a relic from the prior implementations of the constraints system, when all - * constraints either had one or no targets. It used to be called during the main constraint - * solving loop, but is now only used for the remaining cases for a few constraints. - * - * None of the actual calculations of the matrices should be done here! Also, this function is - * not to be used by any new constraints, particularly any that have multiple targets. - */ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph, Scene *scene, bConstraint *con, @@ -6363,7 +6330,6 @@ void BKE_constraint_target_matrix_get(struct Depsgraph *depsgraph, } } -/* Get the list of targets required for solving a constraint */ void BKE_constraint_targets_for_solving_get(struct Depsgraph *depsgraph, bConstraint *con, bConstraintOb *cob, @@ -6433,12 +6399,6 @@ void BKE_constraint_custom_object_space_get(float r_mat[4][4], bConstraint *con) /* ---------- Evaluation ----------- */ -/* This function is called whenever constraints need to be evaluated. Currently, all - * constraints that can be evaluated are every time this gets run. - * - * BKE_constraints_make_evalob and BKE_constraints_clear_evalob should be called before and - * after running this function, to sort out cob - */ void BKE_constraints_solve(struct Depsgraph *depsgraph, ListBase *conlist, bConstraintOb *cob, diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c index c235a1bbb6a..ceaed5d2bba 100644 --- a/source/blender/blenkernel/intern/context.c +++ b/source/blender/blenkernel/intern/context.c @@ -547,12 +547,6 @@ static void data_dir_add(ListBase *lb, const char *member, const bool use_all) BLI_addtail(lb, link); } -/** - * \param C: Context - * \param use_store: Use 'C->wm.store' - * \param use_rna: Use Include the properties from 'RNA_Context' - * \param use_all: Don't skip values (currently only "scene") - */ ListBase CTX_data_dir_get_ex(const bContext *C, const bool use_store, const bool use_rna, @@ -1127,13 +1121,6 @@ RenderEngineType *CTX_data_engine_type(const bContext *C) return RE_engines_find(scene->r.engine); } -/** - * This is tricky. Sometimes the user overrides the render_layer - * but not the scene_collection. In this case what to do? - * - * If the scene_collection is linked to the ViewLayer we use it. - * Otherwise we fallback to the active one of the ViewLayer. - */ LayerCollection *CTX_data_layer_collection(const bContext *C) { ViewLayer *view_layer = CTX_data_view_layer(C); diff --git a/source/blender/blenkernel/intern/crazyspace.c b/source/blender/blenkernel/intern/crazyspace.c index 26894495777..573595b6f90 100644 --- a/source/blender/blenkernel/intern/crazyspace.c +++ b/source/blender/blenkernel/intern/crazyspace.c @@ -41,6 +41,7 @@ #include "BKE_mesh_wrapper.h" #include "BKE_modifier.h" #include "BKE_multires.h" +#include "BKE_report.h" #include "DEG_depsgraph_query.h" @@ -98,7 +99,6 @@ static bool modifiers_disable_subsurf_temporary(struct Scene *scene, Object *ob) return disabled; } -/* disable subsurf temporal, get mapped cos, and enable it */ float (*BKE_crazyspace_get_mapped_editverts(struct Depsgraph *depsgraph, Object *obedit))[3] { Scene *scene = DEG_get_input_scene(depsgraph); @@ -110,7 +110,7 @@ float (*BKE_crazyspace_get_mapped_editverts(struct Depsgraph *depsgraph, Object /* disable subsurf temporal, get mapped cos, and enable it */ if (modifiers_disable_subsurf_temporary(scene_eval, obedit_eval)) { /* need to make new derivemesh */ - makeDerivedMesh(depsgraph, scene_eval, obedit_eval, editmesh_eval, &CD_MASK_BAREMESH); + makeDerivedMesh(depsgraph, scene_eval, obedit_eval, &CD_MASK_BAREMESH); } /* now get the cage */ @@ -243,10 +243,6 @@ void BKE_crazyspace_set_quats_mesh(Mesh *me, } } -/** - * Returns an array of deform matrices for crazy-space correction, - * and the number of modifiers left. - */ int BKE_crazyspace_get_first_deform_matrices_editbmesh(struct Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -521,3 +517,85 @@ void BKE_crazyspace_build_sculpt(struct Depsgraph *depsgraph, } } } + +/* -------------------------------------------------------------------- */ +/** \name Crazyspace API + * \{ */ + +void BKE_crazyspace_api_eval(Depsgraph *depsgraph, + Scene *scene, + Object *object, + struct ReportList *reports) +{ + if (object->runtime.crazyspace_deform_imats != NULL || + object->runtime.crazyspace_deform_cos != NULL) { + return; + } + + if (object->type != OB_MESH) { + BKE_report(reports, + RPT_ERROR, + "Crazyspace transformation is only available for Mesh type of objects"); + return; + } + + const Mesh *mesh = (const Mesh *)object->data; + object->runtime.crazyspace_num_verts = mesh->totvert; + BKE_crazyspace_build_sculpt(depsgraph, + scene, + object, + &object->runtime.crazyspace_deform_imats, + &object->runtime.crazyspace_deform_cos); +} + +void BKE_crazyspace_api_displacement_to_deformed(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement[3], + float r_displacement_deformed[3]) +{ + if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_num_verts) { + BKE_reportf(reports, + RPT_ERROR, + "Invalid vertex index %d (expected to be within 0 to %d range)", + vertex_index, + object->runtime.crazyspace_num_verts); + return; + } + + mul_v3_m3v3(r_displacement_deformed, + object->runtime.crazyspace_deform_imats[vertex_index], + displacement); +} + +void BKE_crazyspace_api_displacement_to_original(struct Object *object, + struct ReportList *reports, + int vertex_index, + float displacement_deformed[3], + float r_displacement[3]) +{ + if (vertex_index < 0 || vertex_index >= object->runtime.crazyspace_num_verts) { + BKE_reportf(reports, + RPT_ERROR, + "Invalid vertex index %d (expected to be within 0 to %d range))", + vertex_index, + object->runtime.crazyspace_num_verts); + return; + } + + float mat[3][3]; + if (!invert_m3_m3(mat, object->runtime.crazyspace_deform_imats[vertex_index])) { + copy_v3_v3(r_displacement, displacement_deformed); + return; + } + + mul_v3_m3v3(r_displacement, mat, displacement_deformed); +} + +void BKE_crazyspace_api_eval_clear(Object *object) +{ + MEM_SAFE_FREE(object->runtime.crazyspace_deform_imats); + MEM_SAFE_FREE(object->runtime.crazyspace_deform_cos); +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/cryptomatte.cc b/source/blender/blenkernel/intern/cryptomatte.cc index 1ff0ca92306..7481d4df351 100644 --- a/source/blender/blenkernel/intern/cryptomatte.cc +++ b/source/blender/blenkernel/intern/cryptomatte.cc @@ -139,7 +139,7 @@ std::optional<std::string> CryptomatteSession::operator[](float encoded_hash) co return std::nullopt; } -CryptomatteSession *BKE_cryptomatte_init(void) +CryptomatteSession *BKE_cryptomatte_init() { CryptomatteSession *session = new CryptomatteSession(); return session; @@ -212,7 +212,6 @@ float BKE_cryptomatte_hash_to_float(uint32_t cryptomatte_hash) return blender::bke::cryptomatte::CryptomatteHash(cryptomatte_hash).float_encoded(); } -/* Find an ID in the given main that matches the given encoded float. */ bool BKE_cryptomatte_find_name(const CryptomatteSession *session, const float encoded_hash, char *r_name, @@ -279,13 +278,13 @@ void BKE_cryptomatte_matte_id_to_entries(NodeCryptomatte *node_storage, const ch token = token.substr(first, (last - first + 1)); if (*token.begin() == '<' && *(--token.end()) == '>') { float encoded_hash = atof(token.substr(1, token.length() - 2).c_str()); - entry = (CryptomatteEntry *)MEM_callocN(sizeof(CryptomatteEntry), __func__); + entry = MEM_cnew<CryptomatteEntry>(__func__); entry->encoded_hash = encoded_hash; } else { const char *name = token.c_str(); int name_len = token.length(); - entry = (CryptomatteEntry *)MEM_callocN(sizeof(CryptomatteEntry), __func__); + entry = MEM_cnew<CryptomatteEntry>(__func__); STRNCPY(entry->name, name); uint32_t hash = BKE_cryptomatte_hash(name, name_len); entry->encoded_hash = BKE_cryptomatte_hash_to_float(hash); @@ -489,10 +488,6 @@ std::string BKE_cryptomatte_meta_data_key(const StringRef layer_name, const Stri return "cryptomatte/" + cryptomatte_layer_name_hash(layer_name) + "/" + key_name; } -/* Extracts the cryptomatte name from a render pass name. - * - * Example: A render pass could be named `CryptoObject00`. This - * function would remove the trailing digits and return `CryptoObject`. */ StringRef BKE_cryptomatte_extract_layer_name(const StringRef render_pass_name) { int64_t last_token = render_pass_name.size(); @@ -525,16 +520,6 @@ std::string CryptomatteHash::hex_encoded() const return encoded.str(); } -/* Convert a cryptomatte hash to a float. - * - * Cryptomatte hashes are stored in float textures and images. The conversion is taken from the - * cryptomatte specification. See Floating point conversion section in - * https://github.com/Psyop/Cryptomatte/blob/master/specification/cryptomatte_specification.pdf. - * - * The conversion uses as many 32 bit floating point values as possible to minimize hash - * collisions. Unfortunately not all 32 bits can be used as NaN and Inf can be problematic. - * - * Note that this conversion assumes to be running on a L-endian system. */ float CryptomatteHash::float_encoded() const { uint32_t mantissa = hash & ((1 << 23) - 1); @@ -626,7 +611,6 @@ void CryptomatteStampDataCallbackData::extract_layer_names(void *_data, data->hash_to_layer_name.add(layer_hash, propvalue); } -/* C type callback function (StampCallback). */ void CryptomatteStampDataCallbackData::extract_layer_manifest(void *_data, const char *propname, char *propvalue, diff --git a/source/blender/blenkernel/intern/curve.c b/source/blender/blenkernel/intern/curve.cc index 0dcfea78ca5..70edaccb244 100644 --- a/source/blender/blenkernel/intern/curve.c +++ b/source/blender/blenkernel/intern/curve.cc @@ -21,15 +21,16 @@ * \ingroup bke */ -#include <math.h> /* floor */ -#include <stdlib.h> -#include <string.h> +#include <cmath> /* floor */ +#include <cstdlib> +#include <cstring> #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_endian_switch.h" #include "BLI_ghash.h" +#include "BLI_index_range.hh" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -43,7 +44,7 @@ #include "DNA_defaults.h" #include "DNA_material_types.h" -/* for dereferencing pointers */ +/* For dereferencing pointers. */ #include "DNA_key_types.h" #include "DNA_object_types.h" #include "DNA_vfont_types.h" @@ -52,13 +53,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" @@ -67,10 +68,12 @@ #include "BLO_read_write.h" +using blender::IndexRange; + /* globals */ /* local */ -static CLG_LogRef LOG = {"bke.curve"}; +// static CLG_LogRef LOG = {"bke.curve"}; static void curve_init_data(ID *id) { @@ -89,12 +92,12 @@ static void curve_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int BLI_listbase_clear(&curve_dst->nurb); BKE_nurbList_duplicate(&(curve_dst->nurb), &(curve_src->nurb)); - curve_dst->mat = MEM_dupallocN(curve_src->mat); + curve_dst->mat = (Material **)MEM_dupallocN(curve_src->mat); - curve_dst->str = MEM_dupallocN(curve_src->str); - curve_dst->strinfo = MEM_dupallocN(curve_src->strinfo); - curve_dst->tb = MEM_dupallocN(curve_src->tb); - curve_dst->batch_cache = NULL; + curve_dst->str = (char *)MEM_dupallocN(curve_src->str); + curve_dst->strinfo = (CharInfo *)MEM_dupallocN(curve_src->strinfo); + curve_dst->tb = (TextBox *)MEM_dupallocN(curve_src->tb); + curve_dst->batch_cache = nullptr; curve_dst->bevel_profile = BKE_curveprofile_copy(curve_src->bevel_profile); @@ -104,8 +107,8 @@ static void curve_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int curve_dst->key->from = &curve_dst->id; } - curve_dst->editnurb = NULL; - curve_dst->editfont = NULL; + curve_dst->editnurb = nullptr; + curve_dst->editfont = nullptr; } static void curve_free_data(ID *id) @@ -130,17 +133,17 @@ 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) @@ -148,9 +151,9 @@ static void curve_blend_write(BlendWriter *writer, ID *id, const void *id_addres Curve *cu = (Curve *)id; /* Clean up, important in undo case to reduce false detection of changed datablocks. */ - cu->editnurb = NULL; - cu->editfont = NULL; - cu->batch_cache = NULL; + cu->editnurb = nullptr; + cu->editfont = nullptr; + cu->batch_cache = nullptr; /* write LibData */ BLO_write_id_struct(writer, Curve, id_address, &cu->id); @@ -188,7 +191,7 @@ static void curve_blend_write(BlendWriter *writer, ID *id, const void *id_addres } } - if (cu->bevel_profile != NULL) { + if (cu->bevel_profile != nullptr) { BKE_curveprofile_blend_write(writer, cu->bevel_profile); } } @@ -218,13 +221,13 @@ static void curve_blend_read_data(BlendDataReader *reader, ID *id) BLO_read_data_address(reader, &cu->strinfo); BLO_read_data_address(reader, &cu->tb); - if (cu->vfont == NULL) { + if (cu->vfont == nullptr) { BLO_read_list(reader, &(cu->nurb)); } else { - cu->nurb.first = cu->nurb.last = NULL; + cu->nurb.first = cu->nurb.last = nullptr; - TextBox *tb = MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "TextBoxread"); + TextBox *tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "TextBoxread"); if (cu->tb) { memcpy(tb, cu->tb, cu->totbox * sizeof(TextBox)); MEM_freeN(cu->tb); @@ -241,16 +244,16 @@ static void curve_blend_read_data(BlendDataReader *reader, ID *id) } } - cu->editnurb = NULL; - cu->editfont = NULL; - cu->batch_cache = NULL; + cu->editnurb = nullptr; + cu->editfont = nullptr; + cu->batch_cache = nullptr; LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { BLO_read_data_address(reader, &nu->bezt); BLO_read_data_address(reader, &nu->bp); BLO_read_data_address(reader, &nu->knotsu); BLO_read_data_address(reader, &nu->knotsv); - if (cu->vfont == NULL) { + if (cu->vfont == nullptr) { nu->charidx = 0; } @@ -261,7 +264,7 @@ static void curve_blend_read_data(BlendDataReader *reader, ID *id) cu->texflag &= ~CU_AUTOSPACE_EVALUATED; BLO_read_data_address(reader, &cu->bevel_profile); - if (cu->bevel_profile != NULL) { + if (cu->bevel_profile != nullptr) { BKE_curveprofile_blend_read(reader, cu->bevel_profile); } } @@ -304,34 +307,35 @@ static void curve_blend_read_expand(BlendExpander *expander, ID *id) } IDTypeInfo IDType_ID_CU = { - .id_code = ID_CU, - .id_filter = FILTER_ID_CU, - .main_listbase_index = INDEX_ID_CU, - .struct_size = sizeof(Curve), - .name = "Curve", - .name_plural = "curves", - .translation_context = BLT_I18NCONTEXT_ID_CURVE, - .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, - - .init_data = curve_init_data, - .copy_data = curve_copy_data, - .free_data = curve_free_data, - .make_local = NULL, - .foreach_id = curve_foreach_id, - .foreach_cache = NULL, - .owner_get = NULL, - - .blend_write = curve_blend_write, - .blend_read_data = curve_blend_read_data, - .blend_read_lib = curve_blend_read_lib, - .blend_read_expand = curve_blend_read_expand, - - .blend_read_undo_preserve = NULL, - - .lib_override_apply_post = NULL, + /* id_code */ ID_CU, + /* id_filter */ FILTER_ID_CU, + /* main_listbase_index */ INDEX_ID_CU, + /* struct_size */ sizeof(Curve), + /* name */ "Curve", + /* name_plural */ "curves", + /* translation_context */ BLT_I18NCONTEXT_ID_CURVE, + /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, + + /* init_data */ curve_init_data, + /* copy_data */ curve_copy_data, + /* free_data */ curve_free_data, + /* make_local */ nullptr, + /* foreach_id */ curve_foreach_id, + /* foreach_cache */ nullptr, + /* foreach_path */ nullptr, + /* owner_get */ nullptr, + + /* blend_write */ curve_blend_write, + /* blend_read_data */ curve_blend_read_data, + /* blend_read_lib */ curve_blend_read_lib, + /* blend_read_expand */ curve_blend_read_expand, + + /* blend_read_undo_preserve */ nullptr, + + /* lib_override_apply_post */ nullptr, }; -/* frees editcurve entirely */ void BKE_curve_editfont_free(Curve *cu) { if (cu->editfont) { @@ -348,21 +352,21 @@ void BKE_curve_editfont_free(Curve *cu) } MEM_freeN(ef); - cu->editfont = NULL; + cu->editfont = nullptr; } } static void curve_editNurb_keyIndex_cv_free_cb(void *val) { - CVKeyIndex *index = val; + CVKeyIndex *index = (CVKeyIndex *)val; MEM_freeN(index->orig_cv); MEM_freeN(val); } void BKE_curve_editNurb_keyIndex_delCV(GHash *keyindex, const void *cv) { - BLI_assert(keyindex != NULL); - BLI_ghash_remove(keyindex, cv, NULL, curve_editNurb_keyIndex_cv_free_cb); + BLI_assert(keyindex != nullptr); + BLI_ghash_remove(keyindex, cv, nullptr, curve_editNurb_keyIndex_cv_free_cb); } void BKE_curve_editNurb_keyIndex_free(GHash **keyindex) @@ -370,8 +374,8 @@ void BKE_curve_editNurb_keyIndex_free(GHash **keyindex) if (!(*keyindex)) { return; } - BLI_ghash_free(*keyindex, NULL, curve_editNurb_keyIndex_cv_free_cb); - *keyindex = NULL; + BLI_ghash_free(*keyindex, nullptr, curve_editNurb_keyIndex_cv_free_cb); + *keyindex = nullptr; } void BKE_curve_editNurb_free(Curve *cu) @@ -380,7 +384,7 @@ void BKE_curve_editNurb_free(Curve *cu) BKE_nurbList_free(&cu->editnurb->nurbs); BKE_curve_editNurb_keyIndex_free(&cu->editnurb->keyindex); MEM_freeN(cu->editnurb); - cu->editnurb = NULL; + cu->editnurb = nullptr; } } @@ -394,12 +398,12 @@ void BKE_curve_init(Curve *cu, const short curve_type) cu->flag |= CU_FRONT | CU_BACK; cu->vfont = cu->vfontb = cu->vfonti = cu->vfontbi = BKE_vfont_builtin_get(); cu->vfont->id.us += 4; - cu->str = MEM_malloc_arrayN(12, sizeof(unsigned char), "str"); + cu->str = (char *)MEM_malloc_arrayN(12, sizeof(unsigned char), "str"); BLI_strncpy(cu->str, "Text", 12); cu->len = cu->len_char32 = cu->pos = 4; - cu->strinfo = MEM_calloc_arrayN(12, sizeof(CharInfo), "strinfo new"); + cu->strinfo = (CharInfo *)MEM_calloc_arrayN(12, sizeof(CharInfo), "strinfo new"); cu->totbox = cu->actbox = 1; - cu->tb = MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "textbox"); + cu->tb = (TextBox *)MEM_calloc_arrayN(MAXTEXTBOX, sizeof(TextBox), "textbox"); cu->tb[0].w = cu->tb[0].h = 0.0; } else if (cu->type == OB_SURF) { @@ -407,7 +411,7 @@ void BKE_curve_init(Curve *cu, const short curve_type) cu->resolu = 4; cu->resolv = 4; } - cu->bevel_profile = NULL; + cu->bevel_profile = nullptr; } Curve *BKE_curve_add(Main *bmain, const char *name, int type) @@ -415,21 +419,20 @@ Curve *BKE_curve_add(Main *bmain, const char *name, int type) Curve *cu; /* We cannot use #BKE_id_new here as we need some custom initialization code. */ - cu = BKE_libblock_alloc(bmain, ID_CU, name, 0); + cu = (Curve *)BKE_libblock_alloc(bmain, ID_CU, name, 0); BKE_curve_init(cu, type); return cu; } -/* Get list of nurbs from editnurbs structure */ ListBase *BKE_curve_editNurbs_get(Curve *cu) { if (cu->editnurb) { return &cu->editnurb->nurbs; } - return NULL; + return nullptr; } const ListBase *BKE_curve_editNurbs_get_for_read(const Curve *cu) @@ -438,7 +441,7 @@ const ListBase *BKE_curve_editNurbs_get_for_read(const Curve *cu) return &cu->editnurb->nurbs; } - return NULL; + return nullptr; } short BKE_curve_type_get(const Curve *cu) @@ -481,10 +484,10 @@ void BKE_curve_dimension_update(Curve *cu) void BKE_curve_type_test(Object *ob) { - ob->type = BKE_curve_type_get(ob->data); + ob->type = BKE_curve_type_get((Curve *)ob->data); if (ob->type == OB_CURVE) { - Curve *cu = ob->data; + Curve *cu = (Curve *)ob->data; if (CU_IS_2D(cu)) { BKE_curve_dimension_update(cu); } @@ -495,15 +498,15 @@ BoundBox *BKE_curve_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) { - Curve *cu = ob->data; + if (ob->runtime.bb == nullptr || ob->runtime.bb->flag & BOUNDBOX_DIRTY) { + Curve *cu = (Curve *)ob->data; float min[3], max[3]; INIT_MINMAX(min, max); BKE_curve_minmax(cu, true, min, max); - 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; @@ -618,26 +621,26 @@ int BKE_nurbList_verts_count_without_handles(const ListBase *nurb) void BKE_nurb_free(Nurb *nu) { - if (nu == NULL) { + if (nu == nullptr) { return; } if (nu->bezt) { MEM_freeN(nu->bezt); } - nu->bezt = NULL; + nu->bezt = nullptr; if (nu->bp) { MEM_freeN(nu->bp); } - nu->bp = NULL; + nu->bp = nullptr; if (nu->knotsu) { MEM_freeN(nu->knotsu); } - nu->knotsu = NULL; + nu->knotsu = nullptr; if (nu->knotsv) { MEM_freeN(nu->knotsv); } - nu->knotsv = NULL; + nu->knotsv = nullptr; // if (nu->trim.first) freeNurblist(&(nu->trim)); MEM_freeN(nu); @@ -645,7 +648,7 @@ void BKE_nurb_free(Nurb *nu) void BKE_nurbList_free(ListBase *lb) { - if (lb == NULL) { + if (lb == nullptr) { return; } @@ -661,8 +664,8 @@ Nurb *BKE_nurb_duplicate(const Nurb *nu) int len; newnu = (Nurb *)MEM_mallocN(sizeof(Nurb), "duplicateNurb"); - if (newnu == NULL) { - return NULL; + if (newnu == nullptr) { + return nullptr; } memcpy(newnu, nu, sizeof(Nurb)); @@ -675,19 +678,19 @@ Nurb *BKE_nurb_duplicate(const Nurb *nu) newnu->bp = (BPoint *)MEM_malloc_arrayN(len, sizeof(BPoint), "duplicateNurb3"); memcpy(newnu->bp, nu->bp, len * sizeof(BPoint)); - newnu->knotsu = newnu->knotsv = NULL; + newnu->knotsu = newnu->knotsv = nullptr; if (nu->knotsu) { len = KNOTSU(nu); if (len) { - newnu->knotsu = MEM_malloc_arrayN(len, sizeof(float), "duplicateNurb4"); + newnu->knotsu = (float *)MEM_malloc_arrayN(len, sizeof(float), "duplicateNurb4"); memcpy(newnu->knotsu, nu->knotsu, sizeof(float) * len); } } if (nu->pntsv > 1 && nu->knotsv) { len = KNOTSV(nu); if (len) { - newnu->knotsv = MEM_malloc_arrayN(len, sizeof(float), "duplicateNurb5"); + newnu->knotsv = (float *)MEM_malloc_arrayN(len, sizeof(float), "duplicateNurb5"); memcpy(newnu->knotsv, nu->knotsv, sizeof(float) * len); } } @@ -695,7 +698,6 @@ Nurb *BKE_nurb_duplicate(const Nurb *nu) return newnu; } -/* copy the nurb but allow for different number of points (to be copied after this) */ Nurb *BKE_nurb_copy(Nurb *src, int pntsu, int pntsv) { Nurb *newnu = (Nurb *)MEM_mallocN(sizeof(Nurb), "copyNurb"); @@ -708,8 +710,8 @@ Nurb *BKE_nurb_copy(Nurb *src, int pntsu, int pntsv) newnu->pntsv = pntsv; /* caller can manually handle these arrays */ - newnu->knotsu = NULL; - newnu->knotsv = NULL; + newnu->knotsu = nullptr; + newnu->knotsv = nullptr; if (src->bezt) { newnu->bezt = (BezTriple *)MEM_malloc_arrayN(pntsu * pntsv, sizeof(BezTriple), "copyNurb2"); @@ -757,10 +759,6 @@ void BKE_nurb_project_2d(Nurb *nu) } } -/** - * if use_radius is truth, minmax will take points' radius into account, - * which will make boundbox closer to beveled curve. - */ void BKE_nurb_minmax(const Nurb *nu, bool use_radius, float min[3], float max[3]) { BezTriple *bezt; @@ -842,7 +840,7 @@ float BKE_nurb_calc_length(const Nurb *nu, int resolution) } } else if (nu->type == CU_BEZIER) { - points = MEM_mallocN(sizeof(float[3]) * (resolu + 1), "getLength_bezier"); + points = (float *)MEM_mallocN(sizeof(float[3]) * (resolu + 1), "getLength_bezier"); a = nu->pntsu - 1; bezt = nu->bezt; if (nu->flagu & CU_NURB_CYCLIC) { @@ -886,9 +884,9 @@ float BKE_nurb_calc_length(const Nurb *nu, int resolution) else if (nu->type == CU_NURBS) { if (nu->pntsv == 1) { /* important to zero for BKE_nurb_makeCurve. */ - points = MEM_callocN(sizeof(float[3]) * pntsu * resolu, "getLength_nurbs"); + points = (float *)MEM_callocN(sizeof(float[3]) * pntsu * resolu, "getLength_nurbs"); - BKE_nurb_makeCurve(nu, points, NULL, NULL, NULL, resolu, sizeof(float[3])); + BKE_nurb_makeCurve(nu, points, nullptr, nullptr, nullptr, resolu, sizeof(float[3])); if (nu->flagu & CU_NURB_CYCLIC) { b = pntsu * resolu + 1; @@ -914,10 +912,9 @@ float BKE_nurb_calc_length(const Nurb *nu, int resolution) return length; } -/* be sure to call makeknots after this */ void BKE_nurb_points_add(Nurb *nu, int number) { - nu->bp = MEM_recallocN(nu->bp, (nu->pntsu + number) * sizeof(BPoint)); + nu->bp = (BPoint *)MEM_recallocN(nu->bp, (nu->pntsu + number) * sizeof(BPoint)); BPoint *bp; int i; @@ -933,7 +930,7 @@ void BKE_nurb_bezierPoints_add(Nurb *nu, int number) BezTriple *bezt; int i; - nu->bezt = MEM_recallocN(nu->bezt, (nu->pntsu + number) * sizeof(BezTriple)); + nu->bezt = (BezTriple *)MEM_recallocN(nu->bezt, (nu->pntsu + number) * sizeof(BezTriple)); for (i = 0, bezt = &nu->bezt[nu->pntsu]; i < number; i++, bezt++) { bezt->radius = 1.0f; @@ -984,7 +981,7 @@ BezTriple *BKE_nurb_bezt_get_next(Nurb *nu, BezTriple *bezt) bezt_next = nu->bezt; } else { - bezt_next = NULL; + bezt_next = nullptr; } } else { @@ -1005,7 +1002,7 @@ BPoint *BKE_nurb_bpoint_get_next(Nurb *nu, BPoint *bp) bp_next = nu->bp; } else { - bp_next = NULL; + bp_next = nullptr; } } else { @@ -1027,7 +1024,7 @@ BezTriple *BKE_nurb_bezt_get_prev(Nurb *nu, BezTriple *bezt) bezt_prev = &nu->bezt[nu->pntsu - 1]; } else { - bezt_prev = NULL; + bezt_prev = nullptr; } } else { @@ -1049,7 +1046,7 @@ BPoint *BKE_nurb_bpoint_get_prev(Nurb *nu, BPoint *bp) bp_prev = &nu->bp[nu->pntsu - 1]; } else { - bp_prev = NULL; + bp_prev = nullptr; } } else { @@ -1166,81 +1163,34 @@ void BKE_nurb_bpoint_calc_plane(struct Nurb *nu, BPoint *bp, float r_plane[3]) static void calcknots(float *knots, const int pnts, const short order, const short flag) { - /* knots: number of pnts NOT corrected for cyclic */ - const int pnts_order = pnts + order; - float k; - int a; + const bool is_cyclic = flag & CU_NURB_CYCLIC; + const bool is_bezier = flag & CU_NURB_BEZIER && !(flag & CU_NURB_ENDPOINT); + const bool is_end_point = flag & CU_NURB_ENDPOINT && !(flag & CU_NURB_BEZIER); + /* Inner knots are always repeated once except on Bezier case. */ + const int repeat_inner = is_bezier ? order - 1 : 1; + /* How many times to repeat 0.0 at the beginning of knot. */ + const int head = is_end_point && !is_cyclic ? order : (is_bezier ? order / 2 : 1); + /* Number of knots replicating widths of the starting knots. + * Covers both Cyclic and EndPoint cases. */ + const int tail = is_cyclic ? 2 * order - 1 : (is_end_point ? order : 0); - switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) { - case CU_NURB_ENDPOINT: - k = 0.0; - for (a = 1; a <= pnts_order; a++) { - knots[a - 1] = k; - if (a >= order && a <= pnts) { - k += 1.0f; - } - } - break; - case CU_NURB_BEZIER: - /* Warning, the order MUST be 2 or 4, - * if this is not enforced, the displist will be corrupt */ - if (order == 4) { - k = 0.34; - for (a = 0; a < pnts_order; a++) { - knots[a] = floorf(k); - k += (1.0f / 3.0f); - } - } - else if (order == 3) { - k = 0.6f; - for (a = 0; a < pnts_order; a++) { - if (a >= order && a <= pnts) { - k += 0.5f; - } - knots[a] = floorf(k); - } - } - else { - CLOG_ERROR(&LOG, "bez nurb curve order is not 3 or 4, should never happen"); - } - break; - default: - for (a = 0; a < pnts_order; a++) { - knots[a] = (float)a; - } - break; - } -} - -static void makecyclicknots(float *knots, int pnts, short order) -/* pnts, order: number of pnts NOT corrected for cyclic */ -{ - int a, b, order2, c; - - if (knots == NULL) { - return; - } + const int knot_count = pnts + order + (is_cyclic ? order - 1 : 0); - order2 = order - 1; + int r = head; + float current = 0.0f; - /* do first long rows (order -1), remove identical knots at endpoints */ - if (order > 2) { - b = pnts + order2; - for (a = 1; a < order2; a++) { - if (knots[b] != knots[b - a]) { - break; - } - } - if (a == order2) { - knots[pnts + order - 2] += 1.0f; + for (const int i : IndexRange(knot_count - tail)) { + knots[i] = current; + r--; + if (r == 0) { + current += 1.0; + r = repeat_inner; } } - b = order; - c = pnts + order + order2; - for (a = pnts + order2; a < c; a++) { - knots[a] = knots[a - 1] + (knots[b] - knots[b - 1]); - b--; + const int tail_index = knot_count - tail; + for (const int i : IndexRange(tail)) { + knots[tail_index + i] = current + (knots[i] - knots[0]); } } @@ -1252,17 +1202,11 @@ static void makeknots(Nurb *nu, short uv) MEM_freeN(nu->knotsu); } if (BKE_nurb_check_valid_u(nu)) { - nu->knotsu = MEM_calloc_arrayN(KNOTSU(nu) + 1, sizeof(float), "makeknots"); - if (nu->flagu & CU_NURB_CYCLIC) { - calcknots(nu->knotsu, nu->pntsu, nu->orderu, 0); /* cyclic should be uniform */ - makecyclicknots(nu->knotsu, nu->pntsu, nu->orderu); - } - else { - calcknots(nu->knotsu, nu->pntsu, nu->orderu, nu->flagu); - } + nu->knotsu = (float *)MEM_calloc_arrayN(KNOTSU(nu) + 1, sizeof(float), "makeknots"); + calcknots(nu->knotsu, nu->pntsu, nu->orderu, nu->flagu); } else { - nu->knotsu = NULL; + nu->knotsu = nullptr; } } else if (uv == 2) { @@ -1270,17 +1214,11 @@ static void makeknots(Nurb *nu, short uv) MEM_freeN(nu->knotsv); } if (BKE_nurb_check_valid_v(nu)) { - nu->knotsv = MEM_calloc_arrayN(KNOTSV(nu) + 1, sizeof(float), "makeknots"); - if (nu->flagv & CU_NURB_CYCLIC) { - calcknots(nu->knotsv, nu->pntsv, nu->orderv, 0); /* cyclic should be uniform */ - makecyclicknots(nu->knotsv, nu->pntsv, nu->orderv); - } - else { - calcknots(nu->knotsv, nu->pntsv, nu->orderv, nu->flagv); - } + nu->knotsv = (float *)MEM_calloc_arrayN(KNOTSV(nu) + 1, sizeof(float), "makeknots"); + calcknots(nu->knotsv, nu->pntsv, nu->orderv, nu->flagv); } else { - nu->knotsv = NULL; + nu->knotsv = nullptr; } } } @@ -1374,9 +1312,6 @@ static void basisNurb( } } -/** - * \param coord_array: has to be (3 * 4 * resolu * resolv) in size, and zero-ed. - */ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int resolu, int resolv) { BPoint *bp; @@ -1387,7 +1322,7 @@ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int r int totu = nu->pntsu * resolu, totv = nu->pntsv * resolv; - if (nu->knotsu == NULL || nu->knotsv == NULL) { + if (nu->knotsu == nullptr || nu->knotsv == nullptr) { return; } if (nu->orderu > nu->pntsu) { @@ -1396,7 +1331,7 @@ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int r if (nu->orderv > nu->pntsv) { return; } - if (coord_array == NULL) { + if (coord_array == nullptr) { return; } @@ -1447,7 +1382,7 @@ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int r jstart = (int *)MEM_malloc_arrayN(totv, sizeof(float), "makeNurbfaces4"); jend = (int *)MEM_malloc_arrayN(totv, sizeof(float), "makeNurbfaces5"); - /* precalculation of basisv and jstart, jend */ + /* Pre-calculation of `basisv` and `jstart`, `jend`. */ if (nu->flagv & CU_NURB_CYCLIC) { cycl = nu->orderv - 1; } @@ -1569,11 +1504,6 @@ void BKE_nurb_makeFaces(const Nurb *nu, float *coord_array, int rowstride, int r MEM_freeN(jend); } -/** - * \param coord_array: Has to be 3 * 4 * pntsu * resolu in size and zero-ed - * \param tilt_array: set when non-NULL - * \param radius_array: set when non-NULL - */ void BKE_nurb_makeCurve(const Nurb *nu, float *coord_array, float *tilt_array, @@ -1590,13 +1520,13 @@ void BKE_nurb_makeCurve(const Nurb *nu, *weight_fp = weight_array; int i, len, istart, iend, cycl; - if (nu->knotsu == NULL) { + if (nu->knotsu == nullptr) { return; } if (nu->orderu > nu->pntsu) { return; } - if (coord_array == NULL) { + if (coord_array == nullptr) { return; } @@ -1690,16 +1620,16 @@ void BKE_nurb_makeCurve(const Nurb *nu, } } - coord_fp = POINTER_OFFSET(coord_fp, stride); + coord_fp = (float *)POINTER_OFFSET(coord_fp, stride); if (tilt_fp) { - tilt_fp = POINTER_OFFSET(tilt_fp, stride); + tilt_fp = (float *)POINTER_OFFSET(tilt_fp, stride); } if (radius_fp) { - radius_fp = POINTER_OFFSET(radius_fp, stride); + radius_fp = (float *)POINTER_OFFSET(radius_fp, stride); } if (weight_fp) { - weight_fp = POINTER_OFFSET(weight_fp, stride); + weight_fp = (float *)POINTER_OFFSET(weight_fp, stride); } u += ustep; @@ -1710,9 +1640,6 @@ void BKE_nurb_makeCurve(const Nurb *nu, MEM_freeN(basisu); } -/** - * Calculate the length for arrays filled in by #BKE_curve_calc_coords_axis. - */ unsigned int BKE_curve_calc_coords_axis_len(const unsigned int bezt_array_len, const unsigned int resolu, const bool is_cyclic, @@ -1724,12 +1651,6 @@ unsigned int BKE_curve_calc_coords_axis_len(const unsigned int bezt_array_len, return points_len; } -/** - * Calculate an array for the entire curve (cyclic or non-cyclic). - * \note Call for each axis. - * - * \param use_cyclic_duplicate_endpoint: Duplicate values at the beginning & end of the array. - */ void BKE_curve_calc_coords_axis(const BezTriple *bezt_array, const unsigned int bezt_array_len, const unsigned int resolu, @@ -1757,7 +1678,7 @@ void BKE_curve_calc_coords_axis(const BezTriple *bezt_array, r_points_offset, (int)resolu, stride); - r_points_offset = POINTER_OFFSET(r_points_offset, resolu_stride); + r_points_offset = (float *)POINTER_OFFSET(r_points_offset, resolu_stride); } if (is_cyclic) { @@ -1770,23 +1691,22 @@ void BKE_curve_calc_coords_axis(const BezTriple *bezt_array, r_points_offset, (int)resolu, stride); - r_points_offset = POINTER_OFFSET(r_points_offset, resolu_stride); + r_points_offset = (float *)POINTER_OFFSET(r_points_offset, resolu_stride); if (use_cyclic_duplicate_endpoint) { *r_points_offset = *r_points; - r_points_offset = POINTER_OFFSET(r_points_offset, stride); + r_points_offset = (float *)POINTER_OFFSET(r_points_offset, stride); } } else { - float *r_points_last = POINTER_OFFSET(r_points, bezt_array_last * resolu_stride); + float *r_points_last = (float *)POINTER_OFFSET(r_points, bezt_array_last * resolu_stride); *r_points_last = bezt_array[bezt_array_last].vec[1][axis]; - r_points_offset = POINTER_OFFSET(r_points_offset, stride); + r_points_offset = (float *)POINTER_OFFSET(r_points_offset, stride); } - BLI_assert(POINTER_OFFSET(r_points, points_len * stride) == r_points_offset); + BLI_assert((float *)POINTER_OFFSET(r_points, points_len * stride) == r_points_offset); UNUSED_VARS_NDEBUG(points_len); } -/* forward differencing method for bezier curve */ void BKE_curve_forward_diff_bezier( float q0, float q1, float q2, float q3, float *p, int it, int stride) { @@ -1808,14 +1728,13 @@ void BKE_curve_forward_diff_bezier( for (a = 0; a <= it; a++) { *p = q0; - p = POINTER_OFFSET(p, stride); + p = (float *)POINTER_OFFSET(p, stride); q0 += q1; q1 += q2; q2 += q3; } } -/* forward differencing method for first derivative of cubic bezier curve */ void BKE_curve_forward_diff_tangent_bezier( float q0, float q1, float q2, float q3, float *p, int it, int stride) { @@ -1834,7 +1753,7 @@ void BKE_curve_forward_diff_tangent_bezier( for (a = 0; a <= it; a++) { *p = q0; - p = POINTER_OFFSET(p, stride); + p = (float *)POINTER_OFFSET(p, stride); q0 += q1; q1 += q2; } @@ -1860,7 +1779,7 @@ static void forward_diff_bezier_cotangent(const float p0[3], (-18.0f * t + 6.0f) * p2[i] + (6.0f * t) * p3[i]; } normalize_v3(p); - p = POINTER_OFFSET(p, stride); + p = (float *)POINTER_OFFSET(p, stride); } } @@ -1973,7 +1892,7 @@ struct BevelSort { static int vergxcobev(const void *a1, const void *a2) { - const struct BevelSort *x1 = a1, *x2 = a2; + const struct BevelSort *x1 = (BevelSort *)a1, *x2 = (BevelSort *)a2; if (x1->left > x2->left) { return 1; @@ -2047,7 +1966,7 @@ static void tilt_bezpart(const BezTriple *prevbezt, float fac, dfac, t[4]; int a; - if (tilt_array == NULL && radius_array == NULL) { + if (tilt_array == nullptr && radius_array == nullptr) { return; } @@ -2095,7 +2014,7 @@ static void tilt_bezpart(const BezTriple *prevbezt, t[3] * next->tilt; } - tilt_array = POINTER_OFFSET(tilt_array, stride); + tilt_array = (float *)POINTER_OFFSET(tilt_array, stride); } if (radius_array) { @@ -2109,14 +2028,14 @@ static void tilt_bezpart(const BezTriple *prevbezt, else { /* reuse interpolation from tilt if we can */ - if (tilt_array == NULL || nu->tilt_interp != nu->radius_interp) { + if (tilt_array == nullptr || nu->tilt_interp != nu->radius_interp) { key_curve_position_weights(fac, t, nu->radius_interp); } *radius_array = t[0] * pprev->radius + t[1] * prevbezt->radius + t[2] * bezt->radius + t[3] * next->radius; } - radius_array = POINTER_OFFSET(radius_array, stride); + radius_array = (float *)POINTER_OFFSET(radius_array, stride); } if (weight_array) { @@ -2124,15 +2043,15 @@ static void tilt_bezpart(const BezTriple *prevbezt, *weight_array = prevbezt->weight + (bezt->weight - prevbezt->weight) * (3.0f * fac * fac - 2.0f * fac * fac * fac); - weight_array = POINTER_OFFSET(weight_array, stride); + weight_array = (float *)POINTER_OFFSET(weight_array, stride); } } } -/* make_bevel_list_3D_* funcs, at a minimum these must - * fill in the bezp->quat and bezp->dir values */ +/* `make_bevel_list_3D_*` functions, at a minimum these must + * fill in the #BevPoint.quat and #BevPoint.dir values. */ -/* utility for make_bevel_list_3D_* funcs */ +/** Utility for `make_bevel_list_3D_*` functions. */ static void bevel_list_calc_bisect(BevList *bl) { BevPoint *bevp2, *bevp1, *bevp0; @@ -2354,14 +2273,14 @@ static void make_bevel_list_3D_minimum_twist(BevList *bl) /* Need to correct for the start/end points not matching * do this by calculating the tilt angle difference, then apply - * the rotation gradually over the entire curve + * the rotation gradually over the entire curve. * - * note that the split is between last and second last, rather than first/last as youd expect. + * Note that the split is between last and second last, rather than first/last as you'd expect. * * real order is like this * 0,1,2,3,4 --> 1,2,3,4,0 * - * this is why we compare last with second last + * This is why we compare last with second last. */ float vec_1[3] = {0, 1, 0}, vec_2[3] = {0, 1, 0}, angle, ang_fac, cross_tmp[3]; @@ -2514,12 +2433,15 @@ static void make_bevel_list_segment_3D(BevList *bl) normalize_v3(bevp1->dir); vec_to_quat(bevp1->quat, bevp1->dir, 5, 1); - axis_angle_to_quat(q, bevp1->dir, bevp1->tilt); mul_qt_qtqt(bevp1->quat, q, bevp1->quat); normalize_qt(bevp1->quat); + copy_v3_v3(bevp2->dir, bevp1->dir); - copy_qt_qt(bevp2->quat, bevp1->quat); + vec_to_quat(bevp2->quat, bevp2->dir, 5, 1); + axis_angle_to_quat(q, bevp2->dir, bevp2->tilt); + mul_qt_qtqt(bevp2->quat, q, bevp2->quat); + normalize_qt(bevp2->quat); } /* only for 2 points */ @@ -2622,13 +2544,13 @@ static void bevlist_firstlast_direction_calc_from_bpoint(const Nurb *nu, BevList void BKE_curve_bevelList_free(ListBase *bev) { LISTBASE_FOREACH_MUTABLE (BevList *, bl, bev) { - if (bl->seglen != NULL) { + if (bl->seglen != nullptr) { MEM_freeN(bl->seglen); } - if (bl->segbevcount != NULL) { + if (bl->segbevcount != nullptr) { MEM_freeN(bl->segbevcount); } - if (bl->bevpoints != NULL) { + if (bl->bevpoints != nullptr) { MEM_freeN(bl->bevpoints); } MEM_freeN(bl); @@ -2639,22 +2561,21 @@ void BKE_curve_bevelList_free(ListBase *bev) void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_render) { - /* - * - convert all curves to polys, with indication of resol and flags for double-vertices - * - possibly; do a smart vertice removal (in case Nurb) - * - separate in individual blocks with BoundBox - * - AutoHole detection + /* - Convert all curves to polys, with indication of resolution and flags for double-vertices. + * - Possibly; do a smart vertex removal (in case #Nurb). + * - Separate in individual blocks with #BoundBox. + * - Auto-hole detection. */ - /* this function needs an object, because of tflag and upflag */ - Curve *cu = ob->data; + /* This function needs an object, because of `tflag` and `upflag`. */ + Curve *cu = (Curve *)ob->data; BezTriple *bezt, *prevbezt; BPoint *bp; BevList *blnew; - BevPoint *bevp2, *bevp1 = NULL, *bevp0; + BevPoint *bevp2, *bevp1 = nullptr, *bevp0; const float threshold = 0.00001f; float min, inp; - float *seglen = NULL; + float *seglen = nullptr; struct BevelSort *sortdata, *sd, *sd1; int a, b, nr, poly, resolu = 0, len = 0, segcount; int *segbevcount; @@ -2662,7 +2583,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ bool is_editmode = false; ListBase *bev; - /* segbevcount alsp requires seglen. */ + /* segbevcount also requires seglen. */ const bool need_seglen = ELEM( cu->bevfac1_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE) || ELEM(cu->bevfac2_mapping, CU_BEVFAC_MAP_SEGMENT, CU_BEVFAC_MAP_SPLINE); @@ -2679,7 +2600,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BKE_curve_bevelList_free(&ob->runtime.curve_cache->bev); if (cu->editnurb && ob->type != OB_FONT) { - is_editmode = 1; + is_editmode = true; } LISTBASE_FOREACH (const Nurb *, nu, nurbs) { @@ -2690,8 +2611,8 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ /* check we are a single point? also check we are not a surface and that the orderu is sane, * enforced in the UI but can go wrong possibly */ if (!BKE_nurb_check_valid_u(nu)) { - BevList *bl = MEM_callocN(sizeof(BevList), "makeBevelList1"); - bl->bevpoints = MEM_calloc_arrayN(1, sizeof(BevPoint), "makeBevelPoints1"); + BevList *bl = (BevList *)MEM_callocN(sizeof(BevList), "makeBevelList1"); + bl->bevpoints = (BevPoint *)MEM_calloc_arrayN(1, sizeof(BevPoint), "makeBevelPoints1"); BLI_addtail(bev, bl); bl->nr = 0; bl->charidx = nu->charidx; @@ -2720,11 +2641,11 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ if (nu->type == CU_POLY) { len = nu->pntsu; - BevList *bl = MEM_callocN(sizeof(BevList), "makeBevelList2"); - bl->bevpoints = MEM_calloc_arrayN(len, sizeof(BevPoint), "makeBevelPoints2"); + BevList *bl = MEM_cnew<BevList>(__func__); + bl->bevpoints = (BevPoint *)MEM_calloc_arrayN(len, sizeof(BevPoint), __func__); if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) { - bl->seglen = MEM_malloc_arrayN(segcount, sizeof(float), "makeBevelList2_seglen"); - bl->segbevcount = MEM_malloc_arrayN(segcount, sizeof(int), "makeBevelList2_segbevcount"); + bl->seglen = (float *)MEM_malloc_arrayN(segcount, sizeof(float), __func__); + bl->segbevcount = (int *)MEM_malloc_arrayN(segcount, sizeof(int), __func__); } BLI_addtail(bev, bl); @@ -2744,7 +2665,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ bevp->radius = bp->radius; bevp->weight = bp->weight; bp++; - if (seglen != NULL && len != 0) { + if (seglen != nullptr && len != 0) { *seglen = len_v3v3(bevp->vec, bp->vec); bevp++; bevp->offset = *seglen; @@ -2770,11 +2691,11 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ /* in case last point is not cyclic */ len = segcount * resolu + 1; - BevList *bl = MEM_callocN(sizeof(BevList), "makeBevelBPoints"); - bl->bevpoints = MEM_calloc_arrayN(len, sizeof(BevPoint), "makeBevelBPointsPoints"); + BevList *bl = MEM_cnew<BevList>(__func__); + bl->bevpoints = (BevPoint *)MEM_calloc_arrayN(len, sizeof(BevPoint), __func__); if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) { - bl->seglen = MEM_malloc_arrayN(segcount, sizeof(float), "makeBevelBPoints_seglen"); - bl->segbevcount = MEM_malloc_arrayN(segcount, sizeof(int), "makeBevelBPoints_segbevcount"); + bl->seglen = (float *)MEM_malloc_arrayN(segcount, sizeof(float), __func__); + bl->segbevcount = (int *)MEM_malloc_arrayN(segcount, sizeof(int), __func__); } BLI_addtail(bev, bl); @@ -2786,7 +2707,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ segbevcount = bl->segbevcount; bevp->offset = 0; - if (seglen != NULL) { + if (seglen != nullptr) { *seglen = 0; *segbevcount = 0; } @@ -2818,7 +2739,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ bevp++; bl->nr++; bl->dupe_nr = 1; - if (seglen != NULL) { + if (seglen != nullptr) { *seglen = len_v3v3(prevbezt->vec[1], bezt->vec[1]); bevp->offset = *seglen; seglen++; @@ -2830,10 +2751,10 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ } } else { - /* always do all three, to prevent data hanging around */ + /* Always do all three, to prevent data hanging around. */ int j; - /* BevPoint must stay aligned to 4 so sizeof(BevPoint)/sizeof(float) works */ + /* #BevPoint must stay aligned to 4 so `sizeof(BevPoint) / sizeof(float)` works. */ for (j = 0; j < 3; j++) { BKE_curve_forward_diff_bezier(prevbezt->vec[1][j], prevbezt->vec[2][j], @@ -2844,13 +2765,13 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ sizeof(BevPoint)); } - /* if both arrays are NULL do nothiong */ + /* If both arrays are `nullptr` do nothing. */ tilt_bezpart(prevbezt, bezt, nu, - do_tilt ? &bevp->tilt : NULL, - do_radius ? &bevp->radius : NULL, - do_weight ? &bevp->weight : NULL, + do_tilt ? &bevp->tilt : nullptr, + do_radius ? &bevp->radius : nullptr, + do_weight ? &bevp->weight : nullptr, resolu, sizeof(BevPoint)); @@ -2864,15 +2785,15 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ sizeof(BevPoint)); } - /* seglen */ - if (seglen != NULL) { + /* `seglen`. */ + if (seglen != nullptr) { *seglen = 0; *segbevcount = 0; for (j = 0; j < resolu; j++) { bevp0 = bevp; bevp++; bevp->offset = len_v3v3(bevp0->vec, bevp->vec); - /* match seglen and segbevcount to the cleaned up bevel lists (see STEP 2) */ + /* Match `seglen` and `segbevcount` to the cleaned up bevel lists (see STEP 2). */ if (bevp->offset > threshold) { *seglen += bevp->offset; *segbevcount += 1; @@ -2906,11 +2827,11 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ if (nu->pntsv == 1) { len = (resolu * segcount); - BevList *bl = MEM_callocN(sizeof(BevList), "makeBevelList3"); - bl->bevpoints = MEM_calloc_arrayN(len, sizeof(BevPoint), "makeBevelPoints3"); + BevList *bl = MEM_cnew<BevList>(__func__); + bl->bevpoints = (BevPoint *)MEM_calloc_arrayN(len, sizeof(BevPoint), __func__); if (need_seglen && (nu->flagu & CU_NURB_CYCLIC) == 0) { - bl->seglen = MEM_malloc_arrayN(segcount, sizeof(float), "makeBevelList3_seglen"); - bl->segbevcount = MEM_malloc_arrayN(segcount, sizeof(int), "makeBevelList3_segbevcount"); + bl->seglen = (float *)MEM_malloc_arrayN(segcount, sizeof(float), __func__); + bl->segbevcount = (int *)MEM_malloc_arrayN(segcount, sizeof(int), __func__); } BLI_addtail(bev, bl); bl->nr = len; @@ -2924,14 +2845,14 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BKE_nurb_makeCurve(nu, &bevp->vec[0], - do_tilt ? &bevp->tilt : NULL, - do_radius ? &bevp->radius : NULL, - do_weight ? &bevp->weight : NULL, + do_tilt ? &bevp->tilt : nullptr, + do_radius ? &bevp->radius : nullptr, + do_weight ? &bevp->weight : nullptr, resolu, sizeof(BevPoint)); /* match seglen and segbevcount to the cleaned up bevel lists (see STEP 2) */ - if (seglen != NULL) { + if (seglen != nullptr) { nr = segcount; bevp0 = bevp; bevp++; @@ -2983,7 +2904,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ } nr--; while (nr--) { - if (seglen != NULL) { + if (seglen != nullptr) { if (fabsf(bevp1->offset) < threshold) { bevp0->dupe_tag = true; bl->dupe_nr++; @@ -3005,10 +2926,10 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ continue; } - nr = bl->nr - bl->dupe_nr + 1; /* +1 because vectorbezier sets flag too */ - blnew = MEM_mallocN(sizeof(BevList), "makeBevelList4"); + nr = bl->nr - bl->dupe_nr + 1; /* +1 because vector-bezier sets flag too. */ + blnew = (BevList *)MEM_mallocN(sizeof(BevList), "makeBevelList4"); memcpy(blnew, bl, sizeof(BevList)); - blnew->bevpoints = MEM_calloc_arrayN(nr, sizeof(BevPoint), "makeBevelPoints4"); + blnew->bevpoints = (BevPoint *)MEM_calloc_arrayN(nr, sizeof(BevPoint), "makeBevelPoints4"); if (!blnew->bevpoints) { MEM_freeN(blnew); break; @@ -3017,7 +2938,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ blnew->seglen = bl->seglen; blnew->nr = 0; BLI_remlink(bev, bl); - BLI_insertlinkbefore(bev, bl->next, blnew); /* to make sure bevlist is tuned with nurblist */ + BLI_insertlinkbefore(bev, bl->next, blnew); /* Ensure `bevlist` is tuned with `nurblist`. */ bevp0 = bl->bevpoints; bevp1 = blnew->bevpoints; nr = bl->nr; @@ -3029,7 +2950,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ } bevp0++; } - if (bl->bevpoints != NULL) { + if (bl->bevpoints != nullptr) { MEM_freeN(bl->bevpoints); } MEM_freeN(bl); @@ -3048,7 +2969,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ /* find extreme left points, also test (turning) direction */ if (poly > 0) { - sd = sortdata = MEM_malloc_arrayN(poly, sizeof(struct BevelSort), "makeBevelList5"); + sd = sortdata = (BevelSort *)MEM_malloc_arrayN(poly, sizeof(struct BevelSort), __func__); LISTBASE_FOREACH (BevList *, bl, bev) { if (bl->poly > 0) { BevPoint *bevp; @@ -3139,7 +3060,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BevPoint *bevp = bl->bevpoints; unit_qt(bevp->quat); } - else if (bl->nr == 2) { /* 2 pnt, treat separate */ + else if (bl->nr == 2) { /* 2 points, treat separately. */ make_bevel_list_segment_2D(bl); } else { @@ -3154,7 +3075,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_ BevPoint *bevp = bl->bevpoints; unit_qt(bevp->quat); } - else if (bl->nr == 2) { /* 2 pnt, treat separate */ + else if (bl->nr == 2) { /* 2 points, treat separately. */ make_bevel_list_segment_3D(bl); } else { @@ -3194,7 +3115,7 @@ static void calchandleNurb_intern(BezTriple *bezt, p2 = bezt->vec[1]; - if (prev == NULL) { + if (prev == nullptr) { p3 = next->vec[1]; pt[0] = 2.0f * p2[0] - p3[0]; pt[1] = 2.0f * p2[1] - p3[1]; @@ -3205,7 +3126,7 @@ static void calchandleNurb_intern(BezTriple *bezt, p1 = prev->vec[1]; } - if (next == NULL) { + if (next == nullptr) { pt[0] = 2.0f * p2[0] - p1[0]; pt[1] = 2.0f * p2[1] - p1[1]; pt[2] = 2.0f * p2[2] - p1[2]; @@ -3283,13 +3204,13 @@ static void calchandleNurb_intern(BezTriple *bezt, if (ydiff1 <= 0.0f) { if (prev->vec[1][1] > bezt->vec[0][1]) { bezt->vec[0][1] = prev->vec[1][1]; - leftviolate = 1; + leftviolate = true; } } else { if (prev->vec[1][1] < bezt->vec[0][1]) { bezt->vec[0][1] = prev->vec[1][1]; - leftviolate = 1; + leftviolate = true; } } } @@ -3310,13 +3231,13 @@ static void calchandleNurb_intern(BezTriple *bezt, if (ydiff1 <= 0.0f) { if (next->vec[1][1] < bezt->vec[2][1]) { bezt->vec[2][1] = next->vec[1][1]; - rightviolate = 1; + rightviolate = true; } } else { if (next->vec[1][1] > bezt->vec[2][1]) { bezt->vec[2][1] = next->vec[1][1]; - rightviolate = 1; + rightviolate = true; } } } @@ -3346,13 +3267,13 @@ 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)) || - /* also when no handles are aligned, skip this step */ + /* When one handle is free, aligning makes no sense, see: T35952 */ + 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, + /* Handles need to be updated during animation and applying stuff like hooks, * but in such situations it's quite difficult to distinguish in which order - * align handles should be aligned so skip them for now */ + * align handles should be aligned so skip them for now. */ return; } @@ -3427,19 +3348,19 @@ static void calchandlesNurb_intern(Nurb *nu, eBezTriple_Flag handle_sel_flag, bo prev = bezt + (a - 1); } else { - prev = NULL; + prev = nullptr; } next = bezt + 1; while (a--) { - calchandleNurb_intern(bezt, prev, next, handle_sel_flag, 0, skip_align, 0); + calchandleNurb_intern(bezt, prev, next, handle_sel_flag, false, skip_align, 0); prev = bezt; if (a == 1) { if (nu->flagu & CU_NURB_CYCLIC) { next = nu->bezt; } else { - next = NULL; + next = nullptr; } } else { @@ -3455,7 +3376,8 @@ static void calchandlesNurb_intern(Nurb *nu, eBezTriple_Flag handle_sel_flag, bo * with easy error checking and de-allocation, and an easy way to add or remove * arrays that are processed in this way when changing code. * - * floats, chars: NULL-terminated arrays of pointers to array pointers that need to be allocated. + * floats, chars: null-terminated arrays of pointers to array pointers that need to be + * allocated. * * Returns: pointer to the buffer that contains all of the arrays. */ @@ -3474,10 +3396,10 @@ static void *allocate_arrays(int count, float ***floats, char ***chars, const ch void *buffer = (float *)MEM_malloc_arrayN(count, (sizeof(float) * num_floats + num_chars), name); if (!buffer) { - return NULL; + return nullptr; } - float *fptr = buffer; + float *fptr = (float *)buffer; for (int i = 0; i < num_floats; i++, fptr += count) { *floats[i] = fptr; @@ -3546,9 +3468,9 @@ static bool tridiagonal_solve_with_limits(float *a, int solve_count) { float *a0, *b0, *c0, *d0; - float **arrays[] = {&a0, &b0, &c0, &d0, NULL}; + float **arrays[] = {&a0, &b0, &c0, &d0, nullptr}; char *is_locked, *num_unlocks; - char **flagarrays[] = {&is_locked, &num_unlocks, NULL}; + char **flagarrays[] = {&is_locked, &num_unlocks, nullptr}; void *tmps = allocate_arrays(solve_count, arrays, flagarrays, "tridiagonal_solve_with_limits"); if (!tmps) { @@ -3815,7 +3737,7 @@ static void bezier_handle_calc_smooth_fcurve( BezTriple *bezt, int total, int start, int count, bool cycle) { float *dx, *dy, *l, *a, *b, *c, *d, *h, *hmax, *hmin; - float **arrays[] = {&dx, &dy, &l, &a, &b, &c, &d, &h, &hmax, &hmin, NULL}; + float **arrays[] = {&dx, &dy, &l, &a, &b, &c, &d, &h, &hmax, &hmin, nullptr}; int solve_count = count; @@ -3844,7 +3766,7 @@ static void bezier_handle_calc_smooth_fcurve( /* allocate all */ - void *tmp_buffer = allocate_arrays(count, arrays, NULL, "bezier_calc_smooth_tmp"); + void *tmp_buffer = allocate_arrays(count, arrays, nullptr, "bezier_calc_smooth_tmp"); if (!tmp_buffer) { return; } @@ -4020,8 +3942,8 @@ void BKE_nurb_handle_smooth_fcurve(BezTriple *bezt, int total, bool cyclic) } } - /* Find continuous subsequences of free auto handles and smooth them, starting at - * search_base. In cyclic mode these subsequences can span the cycle boundary. */ + /* Find continuous sub-sequences of free auto handles and smooth them, starting at search_base. + * In cyclic mode these sub-sequences can span the cycle boundary. */ int start = search_base, count = 1; for (int i = 1, j = start + 1; i < total; i++, j++) { @@ -4046,23 +3968,12 @@ void BKE_nurb_handle_smooth_fcurve(BezTriple *bezt, int total, bool cyclic) } } -/** - * Recalculate the handles of a nurb bezier-triple. Acts based on handle selection with `SELECT` - * flag. To use a different flag, use #BKE_nurb_handle_calc_ex(). - */ void BKE_nurb_handle_calc( BezTriple *bezt, BezTriple *prev, BezTriple *next, const bool is_fcurve, const char smoothing) { - calchandleNurb_intern(bezt, prev, next, SELECT, is_fcurve, false, smoothing); + calchandleNurb_intern(bezt, prev, next, (eBezTriple_Flag)SELECT, is_fcurve, false, smoothing); } -/** - * Variant of #BKE_nurb_handle_calc() that allows calculating based on a different select flag. - * - * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. - * Usually #SELECT, but may want to use a different one at times - * (if caller does not operate on selection). - */ void BKE_nurb_handle_calc_ex(BezTriple *bezt, BezTriple *prev, BezTriple *next, @@ -4070,12 +3981,13 @@ void BKE_nurb_handle_calc_ex(BezTriple *bezt, const bool is_fcurve, const char smoothing) { - calchandleNurb_intern(bezt, prev, next, handle_sel_flag, is_fcurve, false, smoothing); + calchandleNurb_intern( + bezt, prev, next, (eBezTriple_Flag)handle_sel_flag, is_fcurve, false, smoothing); } void BKE_nurb_handles_calc(Nurb *nu) /* first, if needed, set handle flags */ { - calchandlesNurb_intern(nu, SELECT, false); + calchandlesNurb_intern(nu, (eBezTriple_Flag)SELECT, false); } /** @@ -4103,14 +4015,12 @@ static void nurb_handles_calc__align_selected(Nurb *nu) nurbList_handles_swap_select(nu); } -/* similar to BKE_nurb_handle_calc but for curves and - * figures out the previous and next for us */ void BKE_nurb_handle_calc_simple(Nurb *nu, BezTriple *bezt) { if (nu->pntsu > 1) { BezTriple *prev = BKE_nurb_bezt_get_prev(nu, bezt); BezTriple *next = BKE_nurb_bezt_get_next(nu, bezt); - BKE_nurb_handle_calc(bezt, prev, next, 0, 0); + BKE_nurb_handle_calc(bezt, prev, next, false, 0); } } @@ -4129,19 +4039,6 @@ void BKE_nurb_handle_calc_simple_auto(Nurb *nu, BezTriple *bezt) } } -/** - * Update selected handle types to ensure valid state, e.g. deduce "Auto" types to concrete ones. - * Thereby \a sel_flag defines what qualifies as selected. - * Use when something has changed handle positions. - * - * The caller needs to recalculate handles. - * - * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`, - * but may want to use a different one at times (if caller does not operate on - * selection). - * \param use_handle: Check selection state of individual handles, otherwise always update both - * handles if the key is selected. - */ void BKE_nurb_bezt_handle_test(BezTriple *bezt, const eBezTriple_Flag__Alias sel_flag, const bool use_handle, @@ -4223,7 +4120,7 @@ void BKE_nurb_handles_autocalc(Nurb *nu, uint8_t flag) const float eps = 0.0001f; const float eps_sq = eps * eps; - if (nu == NULL || nu->bezt == NULL) { + if (nu == nullptr || nu->bezt == nullptr) { return; } @@ -4238,13 +4135,13 @@ void BKE_nurb_handles_autocalc(Nurb *nu, uint8_t flag) /* left handle: */ if (flag == 0 || (bezt1->f1 & flag)) { bezt1->h1 = HD_FREE; - /* distance too short: vectorhandle */ + /* Distance too short: vector-handle. */ if (len_squared_v3v3(bezt1->vec[1], bezt0->vec[1]) < eps_sq) { bezt1->h1 = HD_VECT; leftsmall = true; } else { - /* aligned handle? */ + /* Aligned handle? */ if (dist_squared_to_line_v3(bezt1->vec[1], bezt1->vec[0], bezt1->vec[2]) < eps_sq) { align = true; bezt1->h1 = HD_ALIGN; @@ -4258,13 +4155,13 @@ void BKE_nurb_handles_autocalc(Nurb *nu, uint8_t flag) /* right handle: */ if (flag == 0 || (bezt1->f3 & flag)) { bezt1->h2 = HD_FREE; - /* distance too short: vectorhandle */ + /* Distance too short: vector-handle. */ if (len_squared_v3v3(bezt1->vec[1], bezt2->vec[1]) < eps_sq) { bezt1->h2 = HD_VECT; rightsmall = true; } else { - /* aligned handle? */ + /* Aligned handle? */ if (align) { bezt1->h2 = HD_ALIGN; } @@ -4305,15 +4202,6 @@ void BKE_nurbList_handles_autocalc(ListBase *editnurb, uint8_t flag) } } -/** - * \param code: - * - 1 (#HD_AUTO): set auto-handle. - * - 2 (#HD_VECT): set vector-handle. - * - 3 (#HD_ALIGN) it toggle, vector-handles become #HD_FREE. - * - * - 5: Set align, like 3 but no toggle. - * - 6: Clear align (setting #HD_FREE), like 3 but no toggle. - */ void BKE_nurbList_handles_set(ListBase *editnurb, const char code) { BezTriple *bezt; @@ -4491,9 +4379,6 @@ void BKE_nurbList_flag_set(ListBase *editnurb, uint8_t flag, bool set) } } -/** - * Set \a flag for every point that already has \a from_flag set. - */ bool BKE_nurbList_flag_set_from_flag(ListBase *editnurb, uint8_t from_flag, uint8_t flag) { bool changed = false; @@ -4608,7 +4493,7 @@ void BKE_nurb_direction_switch(Nurb *nu) /* and make in increasing order again */ a = KNOTSU(nu); fp1 = nu->knotsu; - fp2 = tempf = MEM_malloc_arrayN(a, sizeof(float), "switchdirect"); + fp2 = tempf = (float *)MEM_malloc_arrayN(a, sizeof(float), "switchdirect"); a--; fp2[a] = fp1[a]; while (a--) { @@ -4678,7 +4563,8 @@ void BKE_curve_nurbs_vert_coords_get(const ListBase *lb, float (*vert_coords)[3] float (*BKE_curve_nurbs_vert_coords_alloc(const ListBase *lb, int *r_vert_len))[3] { const int vert_len = BKE_nurbList_verts_count(lb); - float(*vert_coords)[3] = MEM_malloc_arrayN(vert_len, sizeof(*vert_coords), __func__); + float(*vert_coords)[3] = (float(*)[3])MEM_malloc_arrayN( + vert_len, sizeof(*vert_coords), __func__); BKE_curve_nurbs_vert_coords_get(lb, vert_coords, vert_len); *r_vert_len = vert_len; return vert_coords; @@ -4717,7 +4603,7 @@ void BKE_curve_nurbs_vert_coords_apply_with_mat4(ListBase *lb, BKE_nurb_project_2d(nu); } - calchandlesNurb_intern(nu, SELECT, true); + calchandlesNurb_intern(nu, (eBezTriple_Flag)SELECT, true); } } @@ -4753,14 +4639,14 @@ void BKE_curve_nurbs_vert_coords_apply(ListBase *lb, BKE_nurb_project_2d(nu); } - calchandlesNurb_intern(nu, SELECT, true); + calchandlesNurb_intern(nu, (eBezTriple_Flag)SELECT, true); } } float (*BKE_curve_nurbs_key_vert_coords_alloc(const ListBase *lb, float *key, int *r_vert_len))[3] { int vert_len = BKE_nurbList_verts_count(lb); - float(*cos)[3] = MEM_malloc_arrayN(vert_len, sizeof(*cos), __func__); + float(*cos)[3] = (float(*)[3])MEM_malloc_arrayN(vert_len, sizeof(*cos), __func__); float *co = cos[0]; LISTBASE_FOREACH (const Nurb *, nu, lb) { @@ -4910,9 +4796,6 @@ bool BKE_nurb_order_clamp_v(struct Nurb *nu) return changed; } -/** - * \note caller must ensure active vertex remains valid. - */ bool BKE_nurb_type_convert(Nurb *nu, const short type, const bool use_handles, @@ -4923,7 +4806,7 @@ bool BKE_nurb_type_convert(Nurb *nu, int a, c, nr; if (nu->type == CU_POLY) { - if (type == CU_BEZIER) { /* To Bezier with vecthandles. */ + if (type == CU_BEZIER) { /* To Bezier with vector-handles. */ nr = nu->pntsu; bezt = (BezTriple *)MEM_calloc_arrayN(nr, sizeof(BezTriple), "setsplinetype2"); nu->bezt = bezt; @@ -4939,7 +4822,7 @@ bool BKE_nurb_type_convert(Nurb *nu, bezt++; } MEM_freeN(nu->bp); - nu->bp = NULL; + nu->bp = nullptr; nu->pntsu = nr; nu->pntsv = 0; nu->type = CU_BEZIER; @@ -4961,7 +4844,7 @@ bool BKE_nurb_type_convert(Nurb *nu, else if (nu->type == CU_BEZIER) { /* Bezier */ if (ELEM(type, CU_POLY, CU_NURBS)) { nr = use_handles ? (3 * nu->pntsu) : nu->pntsu; - nu->bp = MEM_calloc_arrayN(nr, sizeof(BPoint), "setsplinetype"); + nu->bp = (BPoint *)MEM_calloc_arrayN(nr, sizeof(BPoint), "setsplinetype"); a = nu->pntsu; bezt = nu->bezt; bp = nu->bp; @@ -4993,7 +4876,7 @@ bool BKE_nurb_type_convert(Nurb *nu, bezt++; } MEM_freeN(nu->bezt); - nu->bezt = NULL; + nu->bezt = nullptr; nu->pntsu = nr; nu->pntsv = 1; nu->orderu = 4; @@ -5013,20 +4896,20 @@ bool BKE_nurb_type_convert(Nurb *nu, if (nu->knotsu) { MEM_freeN(nu->knotsu); /* python created nurbs have a knotsu of zero */ } - nu->knotsu = NULL; + nu->knotsu = nullptr; MEM_SAFE_FREE(nu->knotsv); } else if (type == CU_BEZIER) { /* to Bezier */ nr = nu->pntsu / 3; if (nr < 2) { - if (r_err_msg != NULL) { + if (r_err_msg != nullptr) { *r_err_msg = "At least 6 points required for conversion"; } return false; /* conversion impossible */ } - bezt = MEM_calloc_arrayN(nr, sizeof(BezTriple), "setsplinetype2"); + bezt = (BezTriple *)MEM_calloc_arrayN(nr, sizeof(BezTriple), "setsplinetype2"); nu->bezt = bezt; a = nr; bp = nu->bp; @@ -5045,9 +4928,9 @@ bool BKE_nurb_type_convert(Nurb *nu, bezt++; } MEM_freeN(nu->bp); - nu->bp = NULL; + nu->bp = nullptr; MEM_freeN(nu->knotsu); - nu->knotsu = NULL; + nu->knotsu = nullptr; nu->pntsu = nr; nu->type = CU_BEZIER; } @@ -5056,7 +4939,6 @@ bool BKE_nurb_type_convert(Nurb *nu, return true; } -/* Get edit nurbs or normal nurbs list */ ListBase *BKE_curve_nurbs_get(Curve *cu) { if (cu->editnurb) { @@ -5077,7 +4959,7 @@ const ListBase *BKE_curve_nurbs_get_for_read(const Curve *cu) void BKE_curve_nurb_active_set(Curve *cu, const Nurb *nu) { - if (nu == NULL) { + if (nu == nullptr) { cu->actnu = CU_ACT_NONE; } else { @@ -5090,14 +4972,13 @@ void BKE_curve_nurb_active_set(Curve *cu, const Nurb *nu) Nurb *BKE_curve_nurb_active_get(Curve *cu) { ListBase *nurbs = BKE_curve_editNurbs_get(cu); - return BLI_findlink(nurbs, cu->actnu); + return (Nurb *)BLI_findlink(nurbs, cu->actnu); } -/* Get active vert for curve */ void *BKE_curve_vert_active_get(Curve *cu) { - Nurb *nu = NULL; - void *vert = NULL; + Nurb *nu = nullptr; + void *vert = nullptr; BKE_curve_nurb_vert_active_get(cu, &nu, &vert); return vert; @@ -5114,7 +4995,6 @@ int BKE_curve_nurb_vert_index_get(const Nurb *nu, const void *vert) return (BPoint *)vert - nu->bp; } -/* Set active nurb and active vert for curve */ void BKE_curve_nurb_vert_active_set(Curve *cu, const Nurb *nu, const void *vert) { if (nu) { @@ -5132,15 +5012,14 @@ void BKE_curve_nurb_vert_active_set(Curve *cu, const Nurb *nu, const void *vert) } } -/* Get points to the active nurb and active vert for curve. */ bool BKE_curve_nurb_vert_active_get(Curve *cu, Nurb **r_nu, void **r_vert) { - Nurb *nu = NULL; - void *vert = NULL; + Nurb *nu = nullptr; + void *vert = nullptr; if (cu->actvert != CU_ACT_NONE) { ListBase *nurbs = BKE_curve_editNurbs_get(cu); - nu = BLI_findlink(nurbs, cu->actnu); + nu = (Nurb *)BLI_findlink(nurbs, cu->actnu); if (nu) { if (nu->type == CU_BEZIER) { @@ -5157,7 +5036,7 @@ bool BKE_curve_nurb_vert_active_get(Curve *cu, Nurb **r_nu, void **r_vert) *r_nu = nu; *r_vert = vert; - return (*r_vert != NULL); + return (*r_vert != nullptr); } void BKE_curve_nurb_vert_active_validate(Curve *cu) @@ -5167,13 +5046,13 @@ void BKE_curve_nurb_vert_active_validate(Curve *cu) if (BKE_curve_nurb_vert_active_get(cu, &nu, &vert)) { if (nu->type == CU_BEZIER) { - BezTriple *bezt = vert; + BezTriple *bezt = (BezTriple *)vert; if (BEZT_ISSEL_ANY(bezt) == 0) { cu->actvert = CU_ACT_NONE; } } else { - BPoint *bp = vert; + BPoint *bp = (BPoint *)vert; if ((bp->f1 & SELECT) == 0) { cu->actvert = CU_ACT_NONE; } @@ -5185,11 +5064,10 @@ void BKE_curve_nurb_vert_active_validate(Curve *cu) } } -/* basic vertex data functions */ bool BKE_curve_minmax(Curve *cu, bool use_radius, float min[3], float max[3]) { ListBase *nurb_lb = BKE_curve_nurbs_get(cu); - ListBase temp_nurb_lb = {NULL, NULL}; + ListBase temp_nurb_lb = {nullptr, nullptr}; const bool is_font = (BLI_listbase_is_empty(nurb_lb)) && (cu->len != 0); /* For font curves we generate temp list of splines. * @@ -5198,7 +5076,7 @@ bool BKE_curve_minmax(Curve *cu, bool use_radius, float min[3], float max[3]) */ if (is_font) { nurb_lb = &temp_nurb_lb; - BKE_vfont_to_curve_ex(NULL, cu, FO_EDIT, nurb_lb, NULL, NULL, NULL, NULL); + BKE_vfont_to_curve_ex(nullptr, cu, FO_EDIT, nurb_lb, nullptr, nullptr, nullptr, nullptr); use_radius = false; } /* Do bounding box based on splines. */ @@ -5303,7 +5181,7 @@ void BKE_curve_transform_ex(Curve *cu, if (do_keys && cu->key) { LISTBASE_FOREACH (KeyBlock *, kb, &cu->key->block) { - float *fp = kb->data; + float *fp = (float *)kb->data; int n = kb->totelem; LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { @@ -5361,7 +5239,7 @@ void BKE_curve_translate(Curve *cu, const float offset[3], const bool do_keys) if (do_keys && cu->key) { LISTBASE_FOREACH (KeyBlock *, kb, &cu->key->block) { - float *fp = kb->data; + float *fp = (float *)kb->data; int n = kb->totelem; LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) { @@ -5513,11 +5391,10 @@ void BKE_curve_material_remap(Curve *cu, const unsigned int *remap, unsigned int } } else { - Nurb *nu; ListBase *nurbs = BKE_curve_editNurbs_get(cu); if (nurbs) { - for (nu = nurbs->first; nu; nu = nu->next) { + LISTBASE_FOREACH (Nurb *, nu, nurbs) { MAT_NR_REMAP(nu->mat_nr); } } @@ -5551,8 +5428,6 @@ void BKE_curve_rect_from_textbox(const struct Curve *cu, r_rect->ymin = r_rect->ymax - tb->h; } -/* This function is almost the same as BKE_fcurve_correct_bezpart(), but doesn't allow as large a - * tangent. */ void BKE_curve_correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2]) { float h1[2], h2[2], len1, len2, len, fac; @@ -5609,8 +5484,8 @@ void BKE_curve_eval_geometry(Depsgraph *depsgraph, Curve *curve) } /* Draw Engine */ -void (*BKE_curve_batch_cache_dirty_tag_cb)(Curve *cu, int mode) = NULL; -void (*BKE_curve_batch_cache_free_cb)(Curve *cu) = NULL; +void (*BKE_curve_batch_cache_dirty_tag_cb)(Curve *cu, int mode) = nullptr; +void (*BKE_curve_batch_cache_free_cb)(Curve *cu) = nullptr; void BKE_curve_batch_cache_dirty_tag(Curve *cu, int mode) { diff --git a/source/blender/blenkernel/intern/curve_bevel.c b/source/blender/blenkernel/intern/curve_bevel.c index d205d8cca46..18e4ab3ade1 100644 --- a/source/blender/blenkernel/intern/curve_bevel.c +++ b/source/blender/blenkernel/intern/curve_bevel.c @@ -64,8 +64,8 @@ static void bevel_quarter_fill(const Curve *curve, float angle = 0.0f; const float dangle = (float)M_PI_2 / (curve->bevresol + 1); for (int i = 0; i < curve->bevresol + 1; i++) { - quarter_coords_x[i] = (float)(cosf(angle) * (curve->ext2)); - quarter_coords_y[i] = (float)(sinf(angle) * (curve->ext2)); + quarter_coords_x[i] = (float)(cosf(angle) * (curve->bevel_radius)); + quarter_coords_y[i] = (float)(sinf(angle) * (curve->bevel_radius)); angle += dangle; } } @@ -76,11 +76,11 @@ static void bevel_quarter_fill(const Curve *curve, /* If there aren't enough samples, the curveprofile won't * sample the start vertex, so set it manually instead. */ - quarter_coords_x[0] = curve->ext2; + quarter_coords_x[0] = curve->bevel_radius; quarter_coords_y[0] = 0.0f; for (int i = 1; i < curve->bevresol + 1; i++) { - quarter_coords_x[i] = (float)(curve->bevel_profile->segments[i].x * (curve->ext2)); - quarter_coords_y[i] = (float)(curve->bevel_profile->segments[i].y * (curve->ext2)); + quarter_coords_x[i] = (float)(curve->bevel_profile->segments[i].x * (curve->bevel_radius)); + quarter_coords_y[i] = (float)(curve->bevel_profile->segments[i].y * (curve->bevel_radius)); } } } @@ -133,13 +133,13 @@ static void curve_bevel_make_extrude_and_fill(const Curve *cu, /* Add the bottom vertex. */ fp[0] = 0.0f; fp[1] = 0.0f; - fp[2] = -cu->ext1 - cu->ext2; + fp[2] = -cu->extrude - cu->bevel_radius; fp += 3; for (int i = cu->bevresol; i >= 0; i--) { fp[0] = 0.0f; fp[1] = quarter_coords_x[i]; - fp[2] = -quarter_coords_y[i] - cu->ext1; + fp[2] = -quarter_coords_y[i] - cu->extrude; fp += 3; } } @@ -147,8 +147,8 @@ static void curve_bevel_make_extrude_and_fill(const Curve *cu, /* Add the extrusion if we're only building either the back or the front. */ if (use_extrude && ELEM(fill_type, FRONT, BACK)) { fp[0] = 0.0f; - fp[1] = cu->ext2; - fp[2] = (fill_type == FRONT) ? -cu->ext1 : cu->ext1; + fp[1] = cu->bevel_radius; + fp[2] = (fill_type == FRONT) ? -cu->extrude : cu->extrude; fp += 3; } @@ -159,13 +159,13 @@ static void curve_bevel_make_extrude_and_fill(const Curve *cu, for (int i = front_start; i < cu->bevresol + 1; i++) { fp[0] = 0.0f; fp[1] = quarter_coords_x[i]; - fp[2] = quarter_coords_y[i] + cu->ext1; + fp[2] = quarter_coords_y[i] + cu->extrude; fp += 3; } /* Add the top vertex. */ fp[0] = 0.0f; fp[1] = 0.0f; - fp[2] = cu->ext1 + cu->ext2; + fp[2] = cu->extrude + cu->bevel_radius; fp += 3; } @@ -174,22 +174,22 @@ static void curve_bevel_make_extrude_and_fill(const Curve *cu, for (int i = cu->bevresol; i > 0; i--) { fp[0] = 0.0f; fp[1] = -quarter_coords_x[i]; - fp[2] = quarter_coords_y[i] + cu->ext1; + fp[2] = quarter_coords_y[i] + cu->extrude; fp += 3; } if (use_extrude) { /* Add the extrusion. */ fp[0] = 0.0f; - fp[1] = -cu->ext2; - fp[2] = cu->ext1; + fp[1] = -cu->bevel_radius; + fp[2] = cu->extrude; fp += 3; } for (int i = 0; i < cu->bevresol + 1; i++) { fp[0] = 0.0f; fp[1] = -quarter_coords_x[i]; - fp[2] = -quarter_coords_y[i] - cu->ext1; + fp[2] = -quarter_coords_y[i] - cu->extrude; fp += 3; } } @@ -213,8 +213,8 @@ static void curve_bevel_make_full_circle(const Curve *cu, ListBase *disp) for (int i = 0; i < nr; i++) { fp[0] = 0.0; - fp[1] = (cosf(angle) * (cu->ext2)); - fp[2] = (sinf(angle) * (cu->ext2)) - cu->ext1; + fp[1] = (cosf(angle) * (cu->bevel_radius)); + fp[2] = (sinf(angle) * (cu->bevel_radius)) - cu->extrude; angle += dangle; fp += 3; } @@ -232,9 +232,9 @@ static void curve_bevel_make_only_extrude(const Curve *cu, ListBase *disp) float *fp = dl->verts; fp[0] = fp[1] = 0.0; - fp[2] = -cu->ext1; + fp[2] = -cu->extrude; fp[3] = fp[4] = 0.0; - fp[5] = cu->ext1; + fp[5] = cu->extrude; } static void curve_bevel_make_from_object(const Curve *cu, ListBase *disp) @@ -247,7 +247,7 @@ static void curve_bevel_make_from_object(const Curve *cu, ListBase *disp) } Curve *bevcu = cu->bevobj->data; - if (bevcu->ext1 == 0.0f && bevcu->ext2 == 0.0f) { + if (bevcu->extrude == 0.0f && bevcu->bevel_radius == 0.0f) { ListBase bevdisp = {NULL, NULL}; float facx = cu->bevobj->scale[0]; float facy = cu->bevobj->scale[1]; @@ -299,8 +299,8 @@ ListBase BKE_curve_bevel_make(const Curve *curve) } } else { - const bool use_extrude = curve->ext1 != 0.0f; - const bool use_bevel = curve->ext2 != 0.0f; + const bool use_extrude = curve->extrude != 0.0f; + const bool use_bevel = curve->bevel_radius != 0.0f; /* Pass. */ if (use_extrude && !use_bevel) { curve_bevel_make_only_extrude(curve, &bevel_shape); 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 28b7c2dfba0..d754f8cc27d 100644 --- a/source/blender/blenkernel/intern/curve_deform.c +++ b/source/blender/blenkernel/intern/curve_deform.c @@ -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]); } @@ -410,12 +410,6 @@ void BKE_curve_deform_coords_with_editmesh(const Object *ob_curve, em_target); } -/** - * \param orco: Input vec and orco = local coord in curve space - * orco is original not-animated or deformed reference point. - * - * The result written in vec and r_mat. - */ void BKE_curve_deform_co(const Object *ob_curve, const Object *ob_target, const float orco[3], diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc index 8eec7f5dfab..e2461adaaca 100644 --- a/source/blender/blenkernel/intern/curve_eval.cc +++ b/source/blender/blenkernel/intern/curve_eval.cc @@ -50,12 +50,6 @@ 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()) { @@ -72,14 +66,18 @@ void CurveEval::resize(const int size) attributes.reallocate(size); } -/** - * \warning Call #reallocate on the spline's attributes after adding all splines. - */ void CurveEval::add_spline(SplinePtr spline) { splines_.append(std::move(spline)); } +void CurveEval::add_splines(MutableSpan<SplinePtr> splines) +{ + for (SplinePtr &spline : splines) { + this->add_spline(std::move(spline)); + } +} + void CurveEval::remove_splines(blender::IndexMask mask) { for (int i = mask.size() - 1; i >= 0; i--) { @@ -102,20 +100,37 @@ void CurveEval::transform(const float4x4 &matrix) } } -void CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const +bool CurveEval::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) const { + bool have_minmax = false; for (const SplinePtr &spline : this->splines()) { - spline->bounds_min_max(min, max, use_evaluated); + if (spline->size()) { + spline->bounds_min_max(min, max, use_evaluated); + have_minmax = true; + } } + + return have_minmax; +} + +float CurveEval::total_length() const +{ + float length = 0.0f; + for (const SplinePtr &spline : this->splines()) { + length += spline->length(); + } + return length; +} + +int CurveEval::total_control_point_size() const +{ + int count = 0; + for (const SplinePtr &spline : this->splines()) { + count += spline->size(); + } + return count; } -/** - * 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. - * - * \note The result array is one longer than the spline count; the last element is the total size. - */ blender::Array<int> CurveEval::control_point_offsets() const { Array<int> offsets(splines_.size() + 1); @@ -128,9 +143,6 @@ blender::Array<int> CurveEval::control_point_offsets() const return offsets; } -/** - * Exactly like #control_point_offsets, but uses the number of evaluated points instead. - */ blender::Array<int> CurveEval::evaluated_point_offsets() const { Array<int> offsets(splines_.size() + 1); @@ -143,11 +155,6 @@ 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); @@ -160,6 +167,13 @@ blender::Array<float> CurveEval::accumulated_spline_lengths() const 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) { @@ -218,8 +232,8 @@ static SplinePtr spline_from_dna_bezier(const Nurb &nurb) Span<const BezTriple> src_points{nurb.bezt, nurb.pntsu}; spline->resize(src_points.size()); MutableSpan<float3> positions = spline->positions(); - MutableSpan<float3> handle_positions_left = spline->handle_positions_left(); - MutableSpan<float3> handle_positions_right = spline->handle_positions_right(); + MutableSpan<float3> handle_positions_left = spline->handle_positions_left(true); + MutableSpan<float3> handle_positions_right = spline->handle_positions_right(true); MutableSpan<BezierSpline::HandleType> handle_types_left = spline->handle_types_left(); MutableSpan<BezierSpline::HandleType> handle_types_right = spline->handle_types_right(); MutableSpan<float> radii = spline->radii(); @@ -336,12 +350,6 @@ std::unique_ptr<CurveEval> curve_eval_from_dna_curve(const Curve &dna_curve) return curve_eval_from_dna_curve(dna_curve, *BKE_curve_nurbs_get_for_read(&dna_curve)); } -/** - * Check the invariants that curve control point attributes should always uphold, necessary - * 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. - */ void CurveEval::assert_valid_point_attributes() const { #ifdef DEBUG @@ -349,25 +357,40 @@ void CurveEval::assert_valid_point_attributes() const return; } const int layer_len = splines_.first()->attributes.data.totlayer; - Map<AttributeIDRef, 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( [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - map.add_or_modify( - attribute_id, - [&](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); - }); + /* 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 } diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 5f2f945192c..073d9d18a04 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -32,8 +32,6 @@ using blender::fn::GMutableSpan; using blender::fn::GSpan; -using blender::fn::GVArray_Typed; -using blender::fn::GVArrayPtr; namespace blender::bke { @@ -58,9 +56,8 @@ static void vert_extrude_to_mesh_data(const Spline &spline, const int vert_offset, const int edge_offset) { - Span<float3> positions = spline.evaluated_positions(); - - for (const int i : IndexRange(positions.size() - 1)) { + 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; @@ -70,13 +67,21 @@ static void vert_extrude_to_mesh_data(const Spline &spline, 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 + positions.size() - 1; + edge.v2 = vert_offset + eval_size - 1; edge.flag = ME_LOOSEEDGE; } - for (const int i : positions.index_range()) { + Span<float3> positions = spline.evaluated_positions(); + Span<float3> tangents = spline.evaluated_tangents(); + Span<float3> normals = spline.evaluated_normals(); + VArray<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, positions[i] + profile_vert); + copy_v3_v3(vert.co, point_matrix * profile_vert); } } @@ -88,6 +93,7 @@ static void mark_edges_sharp(MutableSpan<MEdge> edges) } 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, @@ -180,13 +186,46 @@ static void spline_extrude_to_mesh_data(const ResultInfo &info, } } + 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()); + VArray<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]); @@ -226,14 +265,22 @@ static inline int spline_extrude_edge_size(const Spline &curve, const Spline &pr curve.evaluated_edges_size() * profile.evaluated_points_size(); } -static inline int spline_extrude_loop_size(const Spline &curve, const Spline &profile) +static inline int spline_extrude_loop_size(const Spline &curve, + const Spline &profile, + const bool fill_caps) { - return curve.evaluated_edges_size() * profile.evaluated_edges_size() * 4; + 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) +static inline int spline_extrude_poly_size(const Spline &curve, + const Spline &profile, + const bool fill_caps) { - return curve.evaluated_edges_size() * profile.evaluated_edges_size(); + 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 { @@ -242,7 +289,9 @@ struct ResultOffsets { Array<int> loop; Array<int> poly; }; -static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<SplinePtr> curves) +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); @@ -263,8 +312,8 @@ static ResultOffsets calculate_result_offsets(Span<SplinePtr> profiles, Span<Spl 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]); - poly_offset += spline_extrude_poly_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++; } } @@ -352,10 +401,8 @@ struct ResultAttributes { }; static ResultAttributes create_result_attributes(const CurveEval &curve, const CurveEval &profile, - Mesh &mesh) + MeshComponent &mesh_component) { - 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 @@ -444,8 +491,8 @@ 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(); + GVArray 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); @@ -510,8 +557,8 @@ 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(); + GVArray 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); @@ -642,22 +689,12 @@ static void copy_spline_domain_attributes_to_mesh(const CurveEval &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) +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); + const ResultOffsets offsets = calculate_result_offsets(profiles, curves, fill_caps); if (offsets.vert.last() == 0) { return nullptr; } @@ -669,7 +706,11 @@ Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile) mesh->smoothresh = DEG2RADF(180.0f); BKE_mesh_normals_tag_dirty(mesh); - ResultAttributes attributes = create_result_attributes(curve, profile, *mesh); + /* Create the mesh component for retrieving attributes at this scope, since output attributes + * can keep a reference to the component for updating after retrieving write access. */ + MeshComponent mesh_component; + mesh_component.replace(mesh, GeometryOwnershipType::Editable); + ResultAttributes attributes = create_result_attributes(curve, profile, mesh_component); threading::parallel_for(curves.index_range(), 128, [&](IndexRange curves_range) { for (const int i_spline : curves_range) { @@ -696,6 +737,7 @@ Mesh *curve_to_mesh_sweep(const CurveEval &curve, const CurveEval &profile) }; spline_extrude_to_mesh_data(info, + fill_caps, {mesh->mvert, mesh->totvert}, {mesh->medge, mesh->totedge}, {mesh->mloop, mesh->totloop}, @@ -720,20 +762,19 @@ 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); + spline->resize(1.0f); + spline->positions().fill(float3(0)); + spline->radii().fill(1.0f); + spline->tilts().fill(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); + 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..8f387be41d3 100644 --- a/source/blender/blenkernel/intern/curveprofile.c +++ b/source/blender/blenkernel/intern/curveprofile.cc @@ -21,28 +21,41 @@ * \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 + * \{ */ + +struct CurveProfile *BKE_curveprofile_add(eCurveProfilePresets preset) +{ + CurveProfile *profile = MEM_cnew<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 +75,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,21 +88,39 @@ 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; } -/** - * Move a point's handle, accounting for the alignment of handles with the #HD_ALIGN type. - * - * \param handle_1: Whether to move the 1st or 2nd control point. - * \param delta: The *relative* change in the handle's position. - * \note Requires #BKE_curveprofile_update call after. - * \return Whether the handle moved from its start position. - */ +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); +} + +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 + * \{ */ + bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point, const bool handle_1, const bool snap, @@ -130,14 +161,6 @@ bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point, return false; } -/** - * Moves a control point, accounting for clipping and snapping, and moving free handles. - * - * \param snap: Whether to snap the point to the grid - * \param delta: The *relative* change of the point's location. - * \return Whether the point moved from its start position. - * \note Requires #BKE_curveprofile_update call after. - */ bool BKE_curveprofile_move_point(struct CurveProfile *profile, struct CurveProfilePoint *point, const bool snap, @@ -185,10 +208,6 @@ bool BKE_curveprofile_move_point(struct CurveProfile *profile, return false; } -/** - * Removes a specific point from the path of control points. - * \note Requires #BKE_curveprofile_update call after. - */ bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *point) { /* Must have 2 points minimum. */ @@ -201,8 +220,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); @@ -219,18 +238,11 @@ bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *poi return true; } -/** - * Removes every point in the widget with the supplied flag set, except for the first and last. - * - * \param flag: #CurveProfilePoint.flag. - * - * \note Requires #BKE_curveprofile_update call after. - */ 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; @@ -265,20 +277,13 @@ static void point_init(CurveProfilePoint *point, float x, float y, short flag, c point->h2 = h2; } -/** - * Adds a new point at the specified location. The choice for which points to place the new vertex - * between is made by checking which control point line segment is closest to the new point and - * placing the new vertex in between that segment's points. - * - * \note Requires #BKE_curveprofile_update call after. - */ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float y) { const float new_loc[2] = {x, y}; /* 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 +302,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. */ @@ -327,11 +332,6 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float return new_pt; } -/** - * Sets the handle type of the selected control points. - * \param type_1, type_2: Handle type for the first handle. HD_VECT, HD_AUTO, HD_FREE, or HD_ALIGN. - * \note Requires #BKE_curveprofile_update call after. - */ void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2) { for (int i = 0; i < profile->path_len; i++) { @@ -341,7 +341,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); } } } @@ -354,19 +354,14 @@ static CurveProfilePoint mirror_point(const CurveProfilePoint *point) return new_point; } -/** - * Flips the profile across the diagonal so that its orientation is reversed. - * - * \note Requires #BKE_curveprofile_update call after. - */ void BKE_curveprofile_reverse(CurveProfile *profile) { /* When there are only two points, reversing shouldn't do anything. */ 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; @@ -435,24 +430,16 @@ static void curveprofile_build_steps(CurveProfile *profile) } } -/** - * Reset the view to the clipping rectangle. - */ void BKE_curveprofile_reset_view(CurveProfile *profile) { profile->view_rect = profile->clip_rect; } -/** - * Resets the profile to the current preset. - * - * \note Requires #BKE_curveprofile_update call after. - */ 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 +472,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 +524,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 +567,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 +577,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 +628,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 +654,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 +689,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 +703,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 +713,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) { @@ -812,55 +815,6 @@ void BKE_curveprofile_create_samples(CurveProfile *profile, MEM_freeN(n_samples); } -/** - * 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. - */ void BKE_curveprofile_set_defaults(CurveProfile *profile) { profile->flag = PROF_USE_CLIP; @@ -869,7 +823,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; @@ -881,88 +835,6 @@ void BKE_curveprofile_set_defaults(CurveProfile *profile) profile->changed_timestamp = 0; } -/** - * 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 - * of the sampled points. - */ void BKE_curveprofile_init(CurveProfile *profile, short segments_len) { if (segments_len != profile->segments_len) { @@ -982,7 +854,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 +864,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 +881,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,18 +932,113 @@ void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile, } /** - * Does a single evaluation along the profile's path. - * Travels down (length_portion * path) length and returns the position at that point. - * - * \param length_portion: The portion (0 to 1) of the path's full length to sample at. - * \note Requires #BKE_curveprofile_init or #BKE_curveprofile_update call before to fill table. + * 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; +} + +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); + } +} + void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile, float length_portion, 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 +1046,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 +1077,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.cc index 3bb02e1856b..5e3beab9b72 100644 --- a/source/blender/blenkernel/intern/customdata.c +++ b/source/blender/blenkernel/intern/customdata.cc @@ -44,6 +44,10 @@ #include "BLI_string_utils.h" #include "BLI_utildefines.h" +#ifndef NDEBUG +# include "BLI_dynstr.h" +#endif + #include "BLT_translation.h" #include "BKE_anonymous_attribute.h" @@ -69,11 +73,10 @@ #define CUSTOMDATA_GROW 5 /* ensure typemap size is ok */ -BLI_STATIC_ASSERT(ARRAY_SIZE(((CustomData *)NULL)->typemap) == CD_NUMTYPES, "size mismatch"); +BLI_STATIC_ASSERT(ARRAY_SIZE(((CustomData *)nullptr)->typemap) == CD_NUMTYPES, "size mismatch"); static CLG_LogRef LOG = {"bke.customdata"}; -/** Update mask_dst with layers defined in mask_src (equivalent to a bitwise OR). */ void CustomData_MeshMasks_update(CustomData_MeshMasks *mask_dst, const CustomData_MeshMasks *mask_src) { @@ -84,7 +87,6 @@ void CustomData_MeshMasks_update(CustomData_MeshMasks *mask_dst, mask_dst->lmask |= mask_src->lmask; } -/** Return True if all layers set in \a mask_required are also set in \a mask_ref */ bool CustomData_MeshMasks_are_matching(const CustomData_MeshMasks *mask_ref, const CustomData_MeshMasks *mask_required) { @@ -96,7 +98,7 @@ bool CustomData_MeshMasks_are_matching(const CustomData_MeshMasks *mask_ref, } /********************* Layer type information **********************/ -typedef struct LayerTypeInfo { +struct LayerTypeInfo { int size; /* the memory size of one element of this layer's data */ /** name of the struct used, for file writing */ @@ -107,7 +109,7 @@ typedef struct LayerTypeInfo { /** * default layer name. * - * \note when NULL this is a way to ensure there is only ever one item + * \note when null this is a way to ensure there is only ever one item * see: CustomData_layertype_is_singleton(). */ const char *defaultname; @@ -115,7 +117,7 @@ typedef struct LayerTypeInfo { /** * a function to copy count elements of this layer's data * (deep copy if appropriate) - * if NULL, memcpy is used + * if null, memcpy is used */ cd_copy copy; @@ -130,7 +132,7 @@ typedef struct LayerTypeInfo { /** * a function to interpolate between count source elements of this * layer's data and store the result in dest - * if weights == NULL or sub_weights == NULL, they should default to 1 + * if weights == null or sub_weights == null, they should default to 1 * * weights gives the weight for each element in sources * sub_weights gives the sub-element weights for each element in sources @@ -148,7 +150,7 @@ typedef struct LayerTypeInfo { void (*swap)(void *data, const int *corner_indices); /** - * a function to set a layer's data to default values. if NULL, the + * a function to set a layer's data to default values. if null, the * default is assumed to be all zeros */ void (*set_default)(void *data, int count); @@ -173,9 +175,9 @@ typedef struct LayerTypeInfo { size_t (*filesize)(CDataFile *cdf, const void *data, int count); /** a function to determine max allowed number of layers, - * should be NULL or return -1 if no limit */ - int (*layers_max)(void); -} LayerTypeInfo; + * should be null or return -1 if no limit */ + int (*layers_max)(); +}; static void layerCopy_mdeformvert(const void *source, void *dest, int count) { @@ -184,17 +186,17 @@ static void layerCopy_mdeformvert(const void *source, void *dest, int count) memcpy(dest, source, count * size); for (i = 0; i < count; i++) { - MDeformVert *dvert = POINTER_OFFSET(dest, i * size); + MDeformVert *dvert = static_cast<MDeformVert *>(POINTER_OFFSET(dest, i * size)); if (dvert->totweight) { - MDeformWeight *dw = MEM_malloc_arrayN( - dvert->totweight, sizeof(*dw), "layerCopy_mdeformvert dw"); + MDeformWeight *dw = static_cast<MDeformWeight *>( + MEM_malloc_arrayN(dvert->totweight, sizeof(*dw), __func__)); memcpy(dw, dvert->dw, dvert->totweight * sizeof(*dw)); dvert->dw = dw; } else { - dvert->dw = NULL; + dvert->dw = nullptr; } } } @@ -202,11 +204,11 @@ static void layerCopy_mdeformvert(const void *source, void *dest, int count) static void layerFree_mdeformvert(void *data, int count, int size) { for (int i = 0; i < count; i++) { - MDeformVert *dvert = POINTER_OFFSET(data, i * size); + MDeformVert *dvert = static_cast<MDeformVert *>(POINTER_OFFSET(data, i * size)); if (dvert->dw) { MEM_freeN(dvert->dw); - dvert->dw = NULL; + dvert->dw = nullptr; dvert->totweight = 0; } } @@ -218,8 +220,8 @@ static void layerCopy_bmesh_elem_py_ptr(const void *UNUSED(source), void *dest, const int size = sizeof(void *); for (int i = 0; i < count; i++) { - void **ptr = POINTER_OFFSET(dest, i * size); - *ptr = NULL; + void **ptr = (void **)POINTER_OFFSET(dest, i * size); + *ptr = nullptr; } } @@ -233,9 +235,9 @@ void bpy_bm_generic_invalidate(struct BPy_BMGeneric *UNUSED(self)) static void layerFree_bmesh_elem_py_ptr(void *data, int count, int size) { for (int i = 0; i < count; i++) { - void **ptr = POINTER_OFFSET(data, i * size); + void **ptr = (void **)POINTER_OFFSET(data, i * size); if (*ptr) { - bpy_bm_generic_invalidate(*ptr); + bpy_bm_generic_invalidate(static_cast<BPy_BMGeneric *>(*ptr)); } } } @@ -253,14 +255,14 @@ static void layerInterp_mdeformvert(const void **sources, MDeformWeight dw; }; - MDeformVert *dvert = dest; - struct MDeformWeight_Link *dest_dwlink = NULL; + MDeformVert *dvert = static_cast<MDeformVert *>(dest); + struct MDeformWeight_Link *dest_dwlink = nullptr; struct MDeformWeight_Link *node; /* build a list of unique def_nrs for dest */ int totweight = 0; for (int i = 0; i < count; i++) { - const MDeformVert *source = sources[i]; + const MDeformVert *source = static_cast<const MDeformVert *>(sources[i]); float interp_weight = weights[i]; for (int j = 0; j < source->totweight; j++) { @@ -282,11 +284,12 @@ static void layerInterp_mdeformvert(const void **sources, /* if this def_nr is not in the list, add it */ if (!node) { - struct MDeformWeight_Link *tmp_dwlink = alloca(sizeof(*tmp_dwlink)); + struct MDeformWeight_Link *tmp_dwlink = static_cast<MDeformWeight_Link *>( + alloca(sizeof(*tmp_dwlink))); tmp_dwlink->dw.def_nr = dw->def_nr; tmp_dwlink->dw.weight = weight; - /* inline linklist */ + /* Inline linked-list. */ tmp_dwlink->next = dest_dwlink; dest_dwlink = tmp_dwlink; @@ -307,7 +310,8 @@ static void layerInterp_mdeformvert(const void **sources, } if (totweight) { - dvert->dw = MEM_malloc_arrayN(totweight, sizeof(*dvert->dw), __func__); + dvert->dw = static_cast<MDeformWeight *>( + MEM_malloc_arrayN(totweight, sizeof(*dvert->dw), __func__)); } } @@ -345,37 +349,13 @@ static void layerInterp_normal(const void **sources, normalize_v3_v3((float *)dest, no); } -static bool layerValidate_normal(void *data, const uint totitems, const bool do_fixes) -{ - static const float no_default[3] = {0.0f, 0.0f, 1.0f}; /* Z-up default normal... */ - float(*no)[3] = data; - bool has_errors = false; - - for (int i = 0; i < totitems; i++, no++) { - if (!is_finite_v3((float *)no)) { - has_errors = true; - if (do_fixes) { - copy_v3_v3((float *)no, no_default); - } - } - else if (!compare_ff(len_squared_v3((float *)no), 1.0f, 1e-6f)) { - has_errors = true; - if (do_fixes) { - normalize_v3((float *)no); - } - } - } - - return has_errors; -} - static void layerCopyValue_normal(const void *source, void *dest, const int mixmode, const float mixfactor) { - const float *no_src = source; - float *no_dst = dest; + const float *no_src = (const float *)source; + float *no_dst = (float *)dest; float no_tmp[3]; if (ELEM(mixmode, @@ -418,13 +398,13 @@ static void layerCopy_tface(const void *source, void *dest, int count) static void layerInterp_tface( const void **sources, const float *weights, const float *sub_weights, int count, void *dest) { - MTFace *tf = dest; + MTFace *tf = static_cast<MTFace *>(dest); float uv[4][2] = {{0.0f}}; const float *sub_weight = sub_weights; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const MTFace *src = sources[i]; + const MTFace *src = static_cast<const MTFace *>(sources[i]); for (int j = 0; j < 4; j++) { if (sub_weights) { @@ -445,7 +425,7 @@ static void layerInterp_tface( static void layerSwap_tface(void *data, const int *corner_indices) { - MTFace *tf = data; + MTFace *tf = static_cast<MTFace *>(data); float uv[4][2]; for (int j = 0; j < 4; j++) { @@ -466,7 +446,7 @@ static void layerDefault_tface(void *data, int count) } } -static int layerMaxNum_tface(void) +static int layerMaxNum_tface() { return MAX_MTFACE; } @@ -493,7 +473,7 @@ static void layerInterp_propFloat(const void **sources, static bool layerValidate_propFloat(void *data, const uint totitems, const bool do_fixes) { - MFloatProperty *fp = data; + MFloatProperty *fp = static_cast<MFloatProperty *>(data); bool has_errors = false; for (int i = 0; i < totitems; i++, fp++) { @@ -531,13 +511,13 @@ static void layerCopy_origspace_face(const void *source, void *dest, int count) static void layerInterp_origspace_face( const void **sources, const float *weights, const float *sub_weights, int count, void *dest) { - OrigSpaceFace *osf = dest; + OrigSpaceFace *osf = static_cast<OrigSpaceFace *>(dest); float uv[4][2] = {{0.0f}}; const float *sub_weight = sub_weights; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const OrigSpaceFace *src = sources[i]; + const OrigSpaceFace *src = static_cast<const OrigSpaceFace *>(sources[i]); for (int j = 0; j < 4; j++) { if (sub_weights) { @@ -557,7 +537,7 @@ static void layerInterp_origspace_face( static void layerSwap_origspace_face(void *data, const int *corner_indices) { - OrigSpaceFace *osf = data; + OrigSpaceFace *osf = static_cast<OrigSpaceFace *>(data); float uv[4][2]; for (int j = 0; j < 4; j++) { @@ -578,7 +558,7 @@ static void layerDefault_origspace_face(void *data, int count) static void layerSwap_mdisps(void *data, const int *ci) { - MDisps *s = data; + MDisps *s = static_cast<MDisps *>(data); if (s->disps) { int nverts = (ci[1] == 3) ? 4 : 3; /* silly way to know vertex count of face */ @@ -591,11 +571,11 @@ static void layerSwap_mdisps(void *data, const int *ci) MEM_freeN(s->disps); s->totdisp = (s->totdisp / corners) * nverts; - s->disps = MEM_calloc_arrayN(s->totdisp, sizeof(float[3]), "mdisp swap"); + s->disps = (float(*)[3])MEM_calloc_arrayN(s->totdisp, sizeof(float[3]), "mdisp swap"); return; } - float(*d)[3] = MEM_calloc_arrayN(s->totdisp, sizeof(float[3]), "mdisps swap"); + float(*d)[3] = (float(*)[3])MEM_calloc_arrayN(s->totdisp, sizeof(float[3]), "mdisps swap"); for (int S = 0; S < corners; S++) { memcpy(d + cornersize * S, s->disps + cornersize * ci[S], sizeof(float[3]) * cornersize); @@ -608,17 +588,17 @@ static void layerSwap_mdisps(void *data, const int *ci) static void layerCopy_mdisps(const void *source, void *dest, int count) { - const MDisps *s = source; - MDisps *d = dest; + const MDisps *s = static_cast<const MDisps *>(source); + MDisps *d = static_cast<MDisps *>(dest); for (int i = 0; i < count; i++) { if (s[i].disps) { - d[i].disps = MEM_dupallocN(s[i].disps); - d[i].hidden = MEM_dupallocN(s[i].hidden); + d[i].disps = static_cast<float(*)[3]>(MEM_dupallocN(s[i].disps)); + d[i].hidden = static_cast<unsigned int *>(MEM_dupallocN(s[i].hidden)); } else { - d[i].disps = NULL; - d[i].hidden = NULL; + d[i].disps = nullptr; + d[i].hidden = nullptr; } /* still copy even if not in memory, displacement can be external */ @@ -629,7 +609,7 @@ static void layerCopy_mdisps(const void *source, void *dest, int count) static void layerFree_mdisps(void *data, int count, int UNUSED(size)) { - MDisps *d = data; + MDisps *d = static_cast<MDisps *>(data); for (int i = 0; i < count; i++) { if (d[i].disps) { @@ -638,8 +618,8 @@ static void layerFree_mdisps(void *data, int count, int UNUSED(size)) if (d[i].hidden) { MEM_freeN(d[i].hidden); } - d[i].disps = NULL; - d[i].hidden = NULL; + d[i].disps = nullptr; + d[i].hidden = nullptr; d[i].totdisp = 0; d[i].level = 0; } @@ -647,16 +627,16 @@ static void layerFree_mdisps(void *data, int count, int UNUSED(size)) static bool layerRead_mdisps(CDataFile *cdf, void *data, int count) { - MDisps *d = data; + MDisps *d = static_cast<MDisps *>(data); for (int i = 0; i < count; i++) { if (!d[i].disps) { - d[i].disps = MEM_calloc_arrayN(d[i].totdisp, sizeof(float[3]), "mdisps read"); + d[i].disps = (float(*)[3])MEM_calloc_arrayN(d[i].totdisp, sizeof(float[3]), "mdisps read"); } if (!cdf_read_data(cdf, sizeof(float[3]) * d[i].totdisp, d[i].disps)) { CLOG_ERROR(&LOG, "failed to read multires displacement %d/%d %d", i, count, d[i].totdisp); - return 0; + return false; } } @@ -665,12 +645,12 @@ static bool layerRead_mdisps(CDataFile *cdf, void *data, int count) static bool layerWrite_mdisps(CDataFile *cdf, const void *data, int count) { - const MDisps *d = data; + const MDisps *d = static_cast<const MDisps *>(data); for (int i = 0; i < count; i++) { if (!cdf_write_data(cdf, sizeof(float[3]) * d[i].totdisp, d[i].disps)) { CLOG_ERROR(&LOG, "failed to write multires displacement %d/%d %d", i, count, d[i].totdisp); - return 0; + return false; } } @@ -679,7 +659,7 @@ static bool layerWrite_mdisps(CDataFile *cdf, const void *data, int count) static size_t layerFilesize_mdisps(CDataFile *UNUSED(cdf), const void *data, int count) { - const MDisps *d = data; + const MDisps *d = static_cast<const MDisps *>(data); size_t size = 0; for (int i = 0; i < count; i++) { @@ -697,7 +677,7 @@ static void layerInterp_paint_mask(const void **sources, float mask = 0.0f; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const float *src = sources[i]; + const float *src = static_cast<const float *>(sources[i]); mask += (*src) * interp_weight; } *(float *)dest = mask; @@ -705,16 +685,16 @@ static void layerInterp_paint_mask(const void **sources, static void layerCopy_grid_paint_mask(const void *source, void *dest, int count) { - const GridPaintMask *s = source; - GridPaintMask *d = dest; + const GridPaintMask *s = static_cast<const GridPaintMask *>(source); + GridPaintMask *d = static_cast<GridPaintMask *>(dest); for (int i = 0; i < count; i++) { if (s[i].data) { - d[i].data = MEM_dupallocN(s[i].data); + d[i].data = static_cast<float *>(MEM_dupallocN(s[i].data)); d[i].level = s[i].level; } else { - d[i].data = NULL; + d[i].data = nullptr; d[i].level = 0; } } @@ -722,7 +702,7 @@ static void layerCopy_grid_paint_mask(const void *source, void *dest, int count) static void layerFree_grid_paint_mask(void *data, int count, int UNUSED(size)) { - GridPaintMask *gpm = data; + GridPaintMask *gpm = static_cast<GridPaintMask *>(data); for (int i = 0; i < count; i++) { MEM_SAFE_FREE(gpm[i].data); @@ -736,8 +716,8 @@ static void layerCopyValue_mloopcol(const void *source, const int mixmode, const float mixfactor) { - const MLoopCol *m1 = source; - MLoopCol *m2 = dest; + const MLoopCol *m1 = static_cast<const MLoopCol *>(source); + MLoopCol *m2 = static_cast<MLoopCol *>(dest); unsigned char tmp_col[4]; if (ELEM(mixmode, @@ -791,7 +771,8 @@ static void layerCopyValue_mloopcol(const void *source, static bool layerEqual_mloopcol(const void *data1, const void *data2) { - const MLoopCol *m1 = data1, *m2 = data2; + const MLoopCol *m1 = static_cast<const MLoopCol *>(data1); + const MLoopCol *m2 = static_cast<const MLoopCol *>(data2); float r, g, b, a; r = m1->r - m2->r; @@ -804,7 +785,7 @@ static bool layerEqual_mloopcol(const void *data1, const void *data2) static void layerMultiply_mloopcol(void *data, float fac) { - MLoopCol *m = data; + MLoopCol *m = static_cast<MLoopCol *>(data); m->r = (float)m->r * fac; m->g = (float)m->g * fac; @@ -814,8 +795,8 @@ static void layerMultiply_mloopcol(void *data, float fac) static void layerAdd_mloopcol(void *data1, const void *data2) { - MLoopCol *m = data1; - const MLoopCol *m2 = data2; + MLoopCol *m = static_cast<MLoopCol *>(data1); + const MLoopCol *m2 = static_cast<const MLoopCol *>(data2); m->r += m2->r; m->g += m2->g; @@ -825,8 +806,9 @@ static void layerAdd_mloopcol(void *data1, const void *data2) static void layerDoMinMax_mloopcol(const void *data, void *vmin, void *vmax) { - const MLoopCol *m = data; - MLoopCol *min = vmin, *max = vmax; + const MLoopCol *m = static_cast<const MLoopCol *>(data); + MLoopCol *min = static_cast<MLoopCol *>(vmin); + MLoopCol *max = static_cast<MLoopCol *>(vmax); if (m->r < min->r) { min->r = m->r; @@ -856,7 +838,8 @@ static void layerDoMinMax_mloopcol(const void *data, void *vmin, void *vmax) static void layerInitMinMax_mloopcol(void *vmin, void *vmax) { - MLoopCol *min = vmin, *max = vmax; + MLoopCol *min = static_cast<MLoopCol *>(vmin); + MLoopCol *max = static_cast<MLoopCol *>(vmax); min->r = 255; min->g = 255; @@ -884,7 +867,7 @@ static void layerInterp_mloopcol(const void **sources, int count, void *dest) { - MLoopCol *mc = dest; + MLoopCol *mc = static_cast<MLoopCol *>(dest); struct { float a; float r; @@ -894,7 +877,7 @@ static void layerInterp_mloopcol(const void **sources, for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const MLoopCol *src = sources[i]; + const MLoopCol *src = static_cast<const MLoopCol *>(sources[i]); col.r += src->r * interp_weight; col.g += src->g * interp_weight; col.b += src->b * interp_weight; @@ -911,7 +894,7 @@ static void layerInterp_mloopcol(const void **sources, mc->a = round_fl_to_uchar_clamp(col.a); } -static int layerMaxNum_mloopcol(void) +static int layerMaxNum_mloopcol() { return MAX_MCOL; } @@ -921,8 +904,8 @@ static void layerCopyValue_mloopuv(const void *source, const int mixmode, const float mixfactor) { - const MLoopUV *luv1 = source; - MLoopUV *luv2 = dest; + const MLoopUV *luv1 = static_cast<const MLoopUV *>(source); + MLoopUV *luv2 = static_cast<MLoopUV *>(dest); /* We only support a limited subset of advanced mixing here - * namely the mixfactor interpolation. */ @@ -937,37 +920,40 @@ static void layerCopyValue_mloopuv(const void *source, static bool layerEqual_mloopuv(const void *data1, const void *data2) { - const MLoopUV *luv1 = data1, *luv2 = data2; + const MLoopUV *luv1 = static_cast<const MLoopUV *>(data1); + const MLoopUV *luv2 = static_cast<const MLoopUV *>(data2); return len_squared_v2v2(luv1->uv, luv2->uv) < 0.00001f; } static void layerMultiply_mloopuv(void *data, float fac) { - MLoopUV *luv = data; + MLoopUV *luv = static_cast<MLoopUV *>(data); mul_v2_fl(luv->uv, fac); } static void layerInitMinMax_mloopuv(void *vmin, void *vmax) { - MLoopUV *min = vmin, *max = vmax; + MLoopUV *min = static_cast<MLoopUV *>(vmin); + MLoopUV *max = static_cast<MLoopUV *>(vmax); INIT_MINMAX2(min->uv, max->uv); } static void layerDoMinMax_mloopuv(const void *data, void *vmin, void *vmax) { - const MLoopUV *luv = data; - MLoopUV *min = vmin, *max = vmax; + const MLoopUV *luv = static_cast<const MLoopUV *>(data); + MLoopUV *min = static_cast<MLoopUV *>(vmin); + MLoopUV *max = static_cast<MLoopUV *>(vmax); minmax_v2v2_v2(min->uv, max->uv, luv->uv); } static void layerAdd_mloopuv(void *data1, const void *data2) { - MLoopUV *l1 = data1; - const MLoopUV *l2 = data2; + MLoopUV *l1 = static_cast<MLoopUV *>(data1); + const MLoopUV *l2 = static_cast<const MLoopUV *>(data2); add_v2_v2(l1->uv, l2->uv); } @@ -985,7 +971,7 @@ static void layerInterp_mloopuv(const void **sources, for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const MLoopUV *src = sources[i]; + const MLoopUV *src = static_cast<const MLoopUV *>(sources[i]); madd_v2_v2fl(uv, src->uv, interp_weight); if (interp_weight > 0.0f) { flag |= src->flag; @@ -999,7 +985,7 @@ static void layerInterp_mloopuv(const void **sources, static bool layerValidate_mloopuv(void *data, const uint totitems, const bool do_fixes) { - MLoopUV *uv = data; + MLoopUV *uv = static_cast<MLoopUV *>(data); bool has_errors = false; for (int i = 0; i < totitems; i++, uv++) { @@ -1020,45 +1006,48 @@ static void layerCopyValue_mloop_origspace(const void *source, const int UNUSED(mixmode), const float UNUSED(mixfactor)) { - const OrigSpaceLoop *luv1 = source; - OrigSpaceLoop *luv2 = dest; + const OrigSpaceLoop *luv1 = static_cast<const OrigSpaceLoop *>(source); + OrigSpaceLoop *luv2 = static_cast<OrigSpaceLoop *>(dest); copy_v2_v2(luv2->uv, luv1->uv); } static bool layerEqual_mloop_origspace(const void *data1, const void *data2) { - const OrigSpaceLoop *luv1 = data1, *luv2 = data2; + const OrigSpaceLoop *luv1 = static_cast<const OrigSpaceLoop *>(data1); + const OrigSpaceLoop *luv2 = static_cast<const OrigSpaceLoop *>(data2); return len_squared_v2v2(luv1->uv, luv2->uv) < 0.00001f; } static void layerMultiply_mloop_origspace(void *data, float fac) { - OrigSpaceLoop *luv = data; + OrigSpaceLoop *luv = static_cast<OrigSpaceLoop *>(data); mul_v2_fl(luv->uv, fac); } static void layerInitMinMax_mloop_origspace(void *vmin, void *vmax) { - OrigSpaceLoop *min = vmin, *max = vmax; + OrigSpaceLoop *min = static_cast<OrigSpaceLoop *>(vmin); + OrigSpaceLoop *max = static_cast<OrigSpaceLoop *>(vmax); INIT_MINMAX2(min->uv, max->uv); } static void layerDoMinMax_mloop_origspace(const void *data, void *vmin, void *vmax) { - const OrigSpaceLoop *luv = data; - OrigSpaceLoop *min = vmin, *max = vmax; + const OrigSpaceLoop *luv = static_cast<const OrigSpaceLoop *>(data); + OrigSpaceLoop *min = static_cast<OrigSpaceLoop *>(vmin); + OrigSpaceLoop *max = static_cast<OrigSpaceLoop *>(vmax); minmax_v2v2_v2(min->uv, max->uv, luv->uv); } static void layerAdd_mloop_origspace(void *data1, const void *data2) { - OrigSpaceLoop *l1 = data1; - const OrigSpaceLoop *l2 = data2; + OrigSpaceLoop *l1 = static_cast<OrigSpaceLoop *>(data1); + const OrigSpaceLoop *l2 = static_cast<const OrigSpaceLoop *>(data2); add_v2_v2(l1->uv, l2->uv); } @@ -1074,7 +1063,7 @@ static void layerInterp_mloop_origspace(const void **sources, for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const OrigSpaceLoop *src = sources[i]; + const OrigSpaceLoop *src = static_cast<const OrigSpaceLoop *>(sources[i]); madd_v2_v2fl(uv, src->uv, interp_weight); } @@ -1086,7 +1075,7 @@ static void layerInterp_mloop_origspace(const void **sources, static void layerInterp_mcol( const void **sources, const float *weights, const float *sub_weights, int count, void *dest) { - MCol *mc = dest; + MCol *mc = static_cast<MCol *>(dest); struct { float a; float r; @@ -1100,7 +1089,7 @@ static void layerInterp_mcol( for (int j = 0; j < 4; j++) { if (sub_weights) { - const MCol *src = sources[i]; + const MCol *src = static_cast<const MCol *>(sources[i]); for (int k = 0; k < 4; k++, sub_weight++, src++) { const float w = (*sub_weight) * interp_weight; col[j].a += src->a * w; @@ -1110,7 +1099,7 @@ static void layerInterp_mcol( } } else { - const MCol *src = sources[i]; + const MCol *src = static_cast<const MCol *>(sources[i]); col[j].a += src[j].a * interp_weight; col[j].r += src[j].r * interp_weight; col[j].g += src[j].g * interp_weight; @@ -1133,7 +1122,7 @@ static void layerInterp_mcol( static void layerSwap_mcol(void *data, const int *corner_indices) { - MCol *mcol = data; + MCol *mcol = static_cast<MCol *>(data); MCol col[4]; for (int j = 0; j < 4; j++) { @@ -1207,7 +1196,7 @@ static void layerInterp_shapekey(const void **sources, static void layerDefault_mvert_skin(void *data, int count) { - MVertSkin *vs = data; + MVertSkin *vs = static_cast<MVertSkin *>(data); for (int i = 0; i < count; i++) { copy_v3_fl(vs[i].radius, 0.25f); @@ -1231,20 +1220,20 @@ static void layerInterp_mvert_skin(const void **sources, for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const MVertSkin *vs_src = sources[i]; + const MVertSkin *vs_src = static_cast<const MVertSkin *>(sources[i]); madd_v3_v3fl(radius, vs_src->radius, interp_weight); } /* Delay writing to the destination in case dest is in sources. */ - MVertSkin *vs_dst = dest; + MVertSkin *vs_dst = static_cast<MVertSkin *>(dest); copy_v3_v3(vs_dst->radius, radius); vs_dst->flag &= ~MVERT_SKIN_ROOT; } static void layerSwap_flnor(void *data, const int *corner_indices) { - short(*flnors)[4][3] = data; + short(*flnors)[4][3] = static_cast<short(*)[4][3]>(data); short nors[4][3]; int i = 4; @@ -1268,8 +1257,8 @@ static void layerCopyValue_propcol(const void *source, const int mixmode, const float mixfactor) { - const MPropCol *m1 = source; - MPropCol *m2 = dest; + const MPropCol *m1 = static_cast<const MPropCol *>(source); + MPropCol *m2 = static_cast<MPropCol *>(dest); float tmp_col[4]; if (ELEM(mixmode, @@ -1313,7 +1302,8 @@ static void layerCopyValue_propcol(const void *source, static bool layerEqual_propcol(const void *data1, const void *data2) { - const MPropCol *m1 = data1, *m2 = data2; + const MPropCol *m1 = static_cast<const MPropCol *>(data1); + const MPropCol *m2 = static_cast<const MPropCol *>(data2); float tot = 0; for (int i = 0; i < 4; i++) { @@ -1326,27 +1316,29 @@ static bool layerEqual_propcol(const void *data1, const void *data2) static void layerMultiply_propcol(void *data, float fac) { - MPropCol *m = data; + MPropCol *m = static_cast<MPropCol *>(data); mul_v4_fl(m->color, fac); } static void layerAdd_propcol(void *data1, const void *data2) { - MPropCol *m = data1; - const MPropCol *m2 = data2; + MPropCol *m = static_cast<MPropCol *>(data1); + const MPropCol *m2 = static_cast<const MPropCol *>(data2); add_v4_v4(m->color, m2->color); } static void layerDoMinMax_propcol(const void *data, void *vmin, void *vmax) { - const MPropCol *m = data; - MPropCol *min = vmin, *max = vmax; + const MPropCol *m = static_cast<const MPropCol *>(data); + MPropCol *min = static_cast<MPropCol *>(vmin); + MPropCol *max = static_cast<MPropCol *>(vmax); minmax_v4v4_v4(min->color, max->color, m->color); } static void layerInitMinMax_propcol(void *vmin, void *vmax) { - MPropCol *min = vmin, *max = vmax; + MPropCol *min = static_cast<MPropCol *>(vmin); + MPropCol *max = static_cast<MPropCol *>(vmax); copy_v4_fl(min->color, FLT_MAX); copy_v4_fl(max->color, FLT_MIN); @@ -1368,17 +1360,17 @@ static void layerInterp_propcol(const void **sources, int count, void *dest) { - MPropCol *mc = dest; + MPropCol *mc = static_cast<MPropCol *>(dest); float col[4] = {0.0f, 0.0f, 0.0f, 0.0f}; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const MPropCol *src = sources[i]; + const MPropCol *src = static_cast<const MPropCol *>(sources[i]); madd_v4_v4fl(col, src->color, interp_weight); } copy_v4_v4(mc->color, col); } -static int layerMaxNum_propcol(void) +static int layerMaxNum_propcol() { return MAX_MCOL; } @@ -1392,7 +1384,7 @@ static void layerInterp_propfloat3(const void **sources, vec3f result = {0.0f, 0.0f, 0.0f}; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const vec3f *src = sources[i]; + const vec3f *src = static_cast<const vec3f *>(sources[i]); madd_v3_v3fl(&result.x, &src->x, interp_weight); } copy_v3_v3((float *)dest, &result.x); @@ -1400,7 +1392,7 @@ static void layerInterp_propfloat3(const void **sources, static void layerMultiply_propfloat3(void *data, float fac) { - vec3f *vec = data; + vec3f *vec = static_cast<vec3f *>(data); vec->x *= fac; vec->y *= fac; vec->z *= fac; @@ -1408,8 +1400,8 @@ static void layerMultiply_propfloat3(void *data, float fac) static void layerAdd_propfloat3(void *data1, const void *data2) { - vec3f *vec1 = data1; - const vec3f *vec2 = data2; + vec3f *vec1 = static_cast<vec3f *>(data1); + const vec3f *vec2 = static_cast<const vec3f *>(data2); vec1->x += vec2->x; vec1->y += vec2->y; vec1->z += vec2->z; @@ -1417,7 +1409,7 @@ static void layerAdd_propfloat3(void *data1, const void *data2) static bool layerValidate_propfloat3(void *data, const uint totitems, const bool do_fixes) { - float *values = data; + float *values = static_cast<float *>(data); bool has_errors = false; for (int i = 0; i < totitems * 3; i++) { if (!isfinite(values[i])) { @@ -1439,7 +1431,7 @@ static void layerInterp_propfloat2(const void **sources, vec2f result = {0.0f, 0.0f}; for (int i = 0; i < count; i++) { const float interp_weight = weights[i]; - const vec2f *src = sources[i]; + const vec2f *src = static_cast<const vec2f *>(sources[i]); madd_v2_v2fl(&result.x, &src->x, interp_weight); } copy_v2_v2((float *)dest, &result.x); @@ -1447,22 +1439,22 @@ static void layerInterp_propfloat2(const void **sources, static void layerMultiply_propfloat2(void *data, float fac) { - vec2f *vec = data; + vec2f *vec = static_cast<vec2f *>(data); vec->x *= fac; vec->y *= fac; } static void layerAdd_propfloat2(void *data1, const void *data2) { - vec2f *vec1 = data1; - const vec2f *vec2 = data2; + vec2f *vec1 = static_cast<vec2f *>(data1); + const vec2f *vec2 = static_cast<const vec2f *>(data2); vec1->x += vec2->x; vec1->y += vec2->y; } static bool layerValidate_propfloat2(void *data, const uint totitems, const bool do_fixes) { - float *values = data; + float *values = static_cast<float *>(data); bool has_errors = false; for (int i = 0; i < totitems * 2; i++) { if (!isfinite(values[i])) { @@ -1477,136 +1469,130 @@ static bool layerValidate_propfloat2(void *data, const uint totitems, const bool static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { /* 0: CD_MVERT */ - {sizeof(MVert), "MVert", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(MVert), "MVert", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 1: CD_MSTICKY */ /* DEPRECATED */ - {sizeof(float[2]), "", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[2]), "", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 2: CD_MDEFORMVERT */ {sizeof(MDeformVert), "MDeformVert", 1, - NULL, + nullptr, layerCopy_mdeformvert, layerFree_mdeformvert, layerInterp_mdeformvert, - NULL, - NULL}, + nullptr, + nullptr}, /* 3: CD_MEDGE */ - {sizeof(MEdge), "MEdge", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(MEdge), "MEdge", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 4: CD_MFACE */ - {sizeof(MFace), "MFace", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(MFace), "MFace", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 5: CD_MTFACE */ - {sizeof(MTFace), - "MTFace", - 1, - N_("UVMap"), - layerCopy_tface, - NULL, - layerInterp_tface, - layerSwap_tface, - layerDefault_tface, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - layerMaxNum_tface}, + {sizeof(MTFace), "MTFace", 1, + N_("UVMap"), layerCopy_tface, nullptr, + layerInterp_tface, layerSwap_tface, layerDefault_tface, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, + nullptr, layerMaxNum_tface}, /* 6: CD_MCOL */ /* 4 MCol structs per face */ {sizeof(MCol[4]), "MCol", 4, N_("Col"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mcol, layerSwap_mcol, layerDefault_mcol, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, layerMaxNum_mloopcol}, /* 7: CD_ORIGINDEX */ - {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, layerDefault_origindex}, + {sizeof(int), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, layerDefault_origindex}, /* 8: CD_NORMAL */ /* 3 floats per normal vector */ {sizeof(float[3]), "vec3f", 1, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, layerInterp_normal, - NULL, - NULL, - layerValidate_normal, - NULL, - NULL, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, layerCopyValue_normal}, /* 9: CD_FACEMAP */ - {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, layerDefault_fmap, NULL}, + {sizeof(int), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, layerDefault_fmap, nullptr}, /* 10: CD_PROP_FLOAT */ {sizeof(MFloatProperty), "MFloatProperty", 1, N_("Float"), layerCopy_propFloat, - NULL, + nullptr, layerInterp_propFloat, - NULL, - NULL, + nullptr, + nullptr, layerValidate_propFloat}, /* 11: CD_PROP_INT32 */ - {sizeof(MIntProperty), "MIntProperty", 1, N_("Int"), layerCopy_propInt, NULL, NULL, NULL}, + {sizeof(MIntProperty), + "MIntProperty", + 1, + N_("Int"), + layerCopy_propInt, + nullptr, + nullptr, + nullptr}, /* 12: CD_PROP_STRING */ {sizeof(MStringProperty), "MStringProperty", 1, N_("String"), layerCopy_propString, - NULL, - NULL, - NULL}, + nullptr, + nullptr, + nullptr}, /* 13: CD_ORIGSPACE */ {sizeof(OrigSpaceFace), "OrigSpaceFace", 1, N_("UVMap"), layerCopy_origspace_face, - NULL, + nullptr, layerInterp_origspace_face, layerSwap_origspace_face, layerDefault_origspace_face}, /* 14: CD_ORCO */ - {sizeof(float[3]), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[3]), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 15: CD_MTEXPOLY */ /* DEPRECATED */ /* 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}, + {sizeof(int), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 16: CD_MLOOPUV */ {sizeof(MLoopUV), "MLoopUV", 1, N_("UVMap"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mloopuv, - NULL, - NULL, + nullptr, + nullptr, layerValidate_mloopuv, layerEqual_mloopuv, layerMultiply_mloopuv, @@ -1614,50 +1600,50 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { layerAdd_mloopuv, layerDoMinMax_mloopuv, layerCopyValue_mloopuv, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, layerMaxNum_tface}, /* 17: CD_MLOOPCOL */ {sizeof(MLoopCol), "MLoopCol", 1, N_("Col"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mloopcol, - NULL, + nullptr, layerDefault_mloopcol, - NULL, + nullptr, layerEqual_mloopcol, layerMultiply_mloopcol, layerInitMinMax_mloopcol, layerAdd_mloopcol, layerDoMinMax_mloopcol, layerCopyValue_mloopcol, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, layerMaxNum_mloopcol}, /* 18: CD_TANGENT */ - {sizeof(float[4][4]), "", 0, N_("Tangent"), NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[4][4]), "", 0, N_("Tangent"), nullptr, nullptr, nullptr, nullptr, nullptr}, /* 19: CD_MDISPS */ {sizeof(MDisps), "MDisps", 1, - NULL, + nullptr, layerCopy_mdisps, layerFree_mdisps, - NULL, + nullptr, layerSwap_mdisps, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, layerRead_mdisps, layerWrite_mdisps, layerFilesize_mdisps}, @@ -1666,52 +1652,60 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { "MCol", 4, N_("PreviewCol"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mcol, layerSwap_mcol, layerDefault_mcol}, /* 21: CD_ID_MCOL */ /* DEPRECATED */ - {sizeof(MCol[4]), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(MCol[4]), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 22: CD_TEXTURE_MCOL */ {sizeof(MCol[4]), "MCol", 4, N_("TexturedCol"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mcol, layerSwap_mcol, layerDefault_mcol}, /* 23: CD_CLOTH_ORCO */ - {sizeof(float[3]), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[3]), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 24: CD_RECAST */ - {sizeof(MRecast), "MRecast", 1, N_("Recast"), NULL, NULL, NULL, NULL}, - - /* BMESH ONLY */ + {sizeof(MRecast), "MRecast", 1, N_("Recast"), nullptr, nullptr, nullptr, nullptr}, /* 25: CD_MPOLY */ - {sizeof(MPoly), "MPoly", 1, N_("NGon Face"), NULL, NULL, NULL, NULL, NULL}, + {sizeof(MPoly), "MPoly", 1, N_("NGon Face"), nullptr, nullptr, nullptr, nullptr, nullptr}, /* 26: CD_MLOOP */ - {sizeof(MLoop), "MLoop", 1, N_("NGon Face-Vertex"), NULL, NULL, NULL, NULL, NULL}, + {sizeof(MLoop), + "MLoop", + 1, + N_("NGon Face-Vertex"), + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, /* 27: CD_SHAPE_KEYINDEX */ - {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(int), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 28: CD_SHAPEKEY */ - {sizeof(float[3]), "", 0, N_("ShapeKey"), NULL, NULL, layerInterp_shapekey}, + {sizeof(float[3]), "", 0, N_("ShapeKey"), nullptr, nullptr, layerInterp_shapekey}, /* 29: CD_BWEIGHT */ - {sizeof(float), "", 0, N_("BevelWeight"), NULL, NULL, layerInterp_bweight}, + {sizeof(float), "", 0, N_("BevelWeight"), nullptr, nullptr, layerInterp_bweight}, /* 30: CD_CREASE */ - {sizeof(float), "", 0, N_("SubSurfCrease"), NULL, NULL, layerInterp_bweight}, + /* NOTE: we do not interpolate crease data as it should be either inherited for subdivided + * edges, or for vertex creases, only present on the original vertex. */ + {sizeof(float), "", 0, N_("SubSurfCrease"), nullptr, nullptr, nullptr}, /* 31: CD_ORIGSPACE_MLOOP */ {sizeof(OrigSpaceLoop), "OrigSpaceLoop", 1, N_("OS Loop"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mloop_origspace, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, layerEqual_mloop_origspace, layerMultiply_mloop_origspace, layerInitMinMax_mloop_origspace, @@ -1723,12 +1717,12 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { "MLoopCol", 1, N_("PreviewLoopCol"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_mloopcol, - NULL, + nullptr, layerDefault_mloopcol, - NULL, + nullptr, layerEqual_mloopcol, layerMultiply_mloopcol, layerInitMinMax_mloopcol, @@ -1739,125 +1733,138 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = { {sizeof(void *), "", 1, - NULL, + nullptr, layerCopy_bmesh_elem_py_ptr, layerFree_bmesh_elem_py_ptr, - NULL, - NULL, - NULL}, - - /* END BMESH ONLY */ - + nullptr, + nullptr, + nullptr}, /* 34: CD_PAINT_MASK */ - {sizeof(float), "", 0, NULL, NULL, NULL, layerInterp_paint_mask, NULL, NULL}, + {sizeof(float), "", 0, nullptr, nullptr, nullptr, layerInterp_paint_mask, nullptr, nullptr}, /* 35: CD_GRID_PAINT_MASK */ {sizeof(GridPaintMask), "GridPaintMask", 1, - NULL, + nullptr, layerCopy_grid_paint_mask, layerFree_grid_paint_mask, - NULL, - NULL, - NULL}, + nullptr, + nullptr, + nullptr}, /* 36: CD_MVERT_SKIN */ {sizeof(MVertSkin), "MVertSkin", 1, - NULL, + nullptr, layerCopy_mvert_skin, - NULL, + nullptr, layerInterp_mvert_skin, - NULL, + nullptr, layerDefault_mvert_skin}, /* 37: CD_FREESTYLE_EDGE */ - {sizeof(FreestyleEdge), "FreestyleEdge", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(FreestyleEdge), + "FreestyleEdge", + 1, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, /* 38: CD_FREESTYLE_FACE */ - {sizeof(FreestyleFace), "FreestyleFace", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(FreestyleFace), + "FreestyleFace", + 1, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, /* 39: CD_MLOOPTANGENT */ - {sizeof(float[4]), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[4]), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 40: CD_TESSLOOPNORMAL */ - {sizeof(short[4][3]), "", 0, NULL, NULL, NULL, NULL, layerSwap_flnor, NULL}, + {sizeof(short[4][3]), "", 0, nullptr, nullptr, nullptr, nullptr, layerSwap_flnor, nullptr}, /* 41: CD_CUSTOMLOOPNORMAL */ - {sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(short[2]), "vec2s", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 42: CD_SCULPT_FACE_SETS */ - {sizeof(int), "", 0, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(int), "", 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 43: CD_LOCATION */ - {sizeof(float[3]), "vec3f", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float[3]), "vec3f", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 44: CD_RADIUS */ - {sizeof(float), "MFloatProperty", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float), "MFloatProperty", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 45: CD_HAIRCURVE */ - {sizeof(HairCurve), "HairCurve", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(HairCurve), "HairCurve", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 46: CD_HAIRMAPPING */ - {sizeof(HairMapping), "HairMapping", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(HairMapping), "HairMapping", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 47: CD_PROP_COLOR */ {sizeof(MPropCol), "MPropCol", 1, N_("Color"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_propcol, - NULL, + nullptr, layerDefault_propcol, - NULL, + nullptr, layerEqual_propcol, layerMultiply_propcol, layerInitMinMax_propcol, layerAdd_propcol, layerDoMinMax_propcol, layerCopyValue_propcol, - NULL, - NULL, - NULL, + nullptr, + nullptr, + nullptr, layerMaxNum_propcol}, /* 48: CD_PROP_FLOAT3 */ {sizeof(float[3]), "vec3f", 1, N_("Float3"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_propfloat3, - NULL, - NULL, + nullptr, + nullptr, layerValidate_propfloat3, - NULL, + nullptr, layerMultiply_propfloat3, - NULL, + nullptr, layerAdd_propfloat3}, /* 49: CD_PROP_FLOAT2 */ {sizeof(float[2]), "vec2f", 1, N_("Float2"), - NULL, - NULL, + nullptr, + nullptr, layerInterp_propfloat2, - NULL, - NULL, + nullptr, + nullptr, layerValidate_propfloat2, - NULL, + nullptr, layerMultiply_propfloat2, - NULL, + nullptr, layerAdd_propfloat2}, /* 50: CD_PROP_BOOL */ {sizeof(bool), "bool", 1, N_("Boolean"), - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL}, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}, /* 51: CD_HAIRLENGTH */ - {sizeof(float), "float", 1, NULL, NULL, NULL, NULL, NULL, NULL}, + {sizeof(float), "float", 1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, }; static const char *LAYERTYPENAMES[CD_NUMTYPES] = { @@ -1918,95 +1925,106 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = { }; const CustomData_MeshMasks CD_MASK_BAREMESH = { - .vmask = CD_MASK_MVERT | CD_MASK_BWEIGHT, - .emask = CD_MASK_MEDGE | CD_MASK_BWEIGHT, - .fmask = 0, - .lmask = CD_MASK_MLOOP, - .pmask = CD_MASK_MPOLY | CD_MASK_FACEMAP, + /* vmask */ CD_MASK_MVERT | CD_MASK_BWEIGHT, + /* emask */ CD_MASK_MEDGE | CD_MASK_BWEIGHT, + /* fmask */ 0, + /* pmask */ CD_MASK_MPOLY | CD_MASK_FACEMAP, + /* lmask */ CD_MASK_MLOOP, }; const CustomData_MeshMasks CD_MASK_BAREMESH_ORIGINDEX = { - .vmask = CD_MASK_MVERT | CD_MASK_BWEIGHT | CD_MASK_ORIGINDEX, - .emask = CD_MASK_MEDGE | CD_MASK_BWEIGHT | CD_MASK_ORIGINDEX, - .fmask = 0, - .lmask = CD_MASK_MLOOP, - .pmask = CD_MASK_MPOLY | CD_MASK_FACEMAP | CD_MASK_ORIGINDEX, + /* vmask */ CD_MASK_MVERT | CD_MASK_BWEIGHT | CD_MASK_ORIGINDEX, + /* emask */ CD_MASK_MEDGE | CD_MASK_BWEIGHT | CD_MASK_ORIGINDEX, + /* fmask */ 0, + /* pmask */ CD_MASK_MPOLY | CD_MASK_FACEMAP | CD_MASK_ORIGINDEX, + /* lmask */ CD_MASK_MLOOP, }; const CustomData_MeshMasks CD_MASK_MESH = { - .vmask = (CD_MASK_MVERT | CD_MASK_MDEFORMVERT | CD_MASK_MVERT_SKIN | CD_MASK_PAINT_MASK | - CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR), - .emask = (CD_MASK_MEDGE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), - .fmask = 0, - .lmask = (CD_MASK_MLOOP | CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | - CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), - .pmask = (CD_MASK_MPOLY | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_PROP_ALL | - CD_MASK_SCULPT_FACE_SETS), + /* vmask */ (CD_MASK_MVERT | CD_MASK_MDEFORMVERT | CD_MASK_MVERT_SKIN | CD_MASK_PAINT_MASK | + CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* emask */ (CD_MASK_MEDGE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), + /* fmask */ 0, + /* pmask */ + (CD_MASK_MPOLY | CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_PROP_ALL | + CD_MASK_SCULPT_FACE_SETS), + /* lmask */ + (CD_MASK_MLOOP | CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | + CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; const CustomData_MeshMasks CD_MASK_EDITMESH = { - .vmask = (CD_MASK_MDEFORMVERT | CD_MASK_PAINT_MASK | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY | - CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR), - .emask = (CD_MASK_PROP_ALL), - .fmask = 0, - .lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | - CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), - .pmask = (CD_MASK_FACEMAP | CD_MASK_PROP_ALL | CD_MASK_SCULPT_FACE_SETS), + /* vmask */ (CD_MASK_MDEFORMVERT | CD_MASK_PAINT_MASK | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY | + CD_MASK_SHAPE_KEYINDEX | CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* emask */ (CD_MASK_PROP_ALL), + /* fmask */ 0, + /* pmask */ (CD_MASK_FACEMAP | CD_MASK_PROP_ALL | CD_MASK_SCULPT_FACE_SETS), + /* lmask */ + (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | + CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; const CustomData_MeshMasks CD_MASK_DERIVEDMESH = { - .vmask = (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN | - CD_MASK_PAINT_MASK | CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | CD_MASK_PROP_ALL | - CD_MASK_PROP_COLOR), - .emask = (CD_MASK_ORIGINDEX | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), - .fmask = (CD_MASK_ORIGINDEX | CD_MASK_ORIGSPACE | CD_MASK_PREVIEW_MCOL | CD_MASK_TANGENT), - .lmask = (CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | - CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP | - CD_MASK_PROP_ALL), /* XXX MISSING CD_MASK_MLOOPTANGENT ? */ - .pmask = (CD_MASK_ORIGINDEX | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_PROP_ALL | - CD_MASK_SCULPT_FACE_SETS), + /* vmask */ (CD_MASK_ORIGINDEX | CD_MASK_MDEFORMVERT | CD_MASK_SHAPEKEY | CD_MASK_MVERT_SKIN | + CD_MASK_PAINT_MASK | CD_MASK_ORCO | CD_MASK_CLOTH_ORCO | CD_MASK_PROP_ALL | + CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* emask */ (CD_MASK_ORIGINDEX | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), + /* fmask */ (CD_MASK_ORIGINDEX | CD_MASK_ORIGSPACE | CD_MASK_PREVIEW_MCOL | CD_MASK_TANGENT), + /* pmask */ + (CD_MASK_ORIGINDEX | CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_PROP_ALL | + CD_MASK_SCULPT_FACE_SETS), + /* lmask */ + (CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_PREVIEW_MLOOPCOL | + CD_MASK_ORIGSPACE_MLOOP | CD_MASK_PROP_ALL), /* XXX MISSING CD_MASK_MLOOPTANGENT ? */ }; const CustomData_MeshMasks CD_MASK_BMESH = { - .vmask = (CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY | - CD_MASK_SHAPE_KEYINDEX | CD_MASK_PAINT_MASK | CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR), - .emask = (CD_MASK_BWEIGHT | CD_MASK_CREASE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), - .fmask = 0, - .lmask = (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | - CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), - .pmask = (CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_PROP_ALL | - CD_MASK_SCULPT_FACE_SETS), + /* vmask */ (CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_SHAPEKEY | + CD_MASK_SHAPE_KEYINDEX | CD_MASK_PAINT_MASK | CD_MASK_PROP_ALL | + CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* emask */ (CD_MASK_BWEIGHT | CD_MASK_CREASE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), + /* fmask */ 0, + /* pmask */ + (CD_MASK_FREESTYLE_FACE | CD_MASK_FACEMAP | CD_MASK_PROP_ALL | CD_MASK_SCULPT_FACE_SETS), + /* lmask */ + (CD_MASK_MDISPS | CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | + CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), }; /** * cover values copied by #mesh_loops_to_tessdata */ const CustomData_MeshMasks CD_MASK_FACECORNERS = { - .vmask = 0, - .emask = 0, - .fmask = (CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_PREVIEW_MCOL | CD_MASK_ORIGSPACE | - CD_MASK_TESSLOOPNORMAL | CD_MASK_TANGENT), - .lmask = (CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_PREVIEW_MLOOPCOL | - CD_MASK_ORIGSPACE_MLOOP | CD_MASK_NORMAL | CD_MASK_MLOOPTANGENT), - .pmask = 0, + /* vmask */ 0, + /* emask */ 0, + /* fmask */ + (CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_PREVIEW_MCOL | CD_MASK_ORIGSPACE | + CD_MASK_TESSLOOPNORMAL | CD_MASK_TANGENT), + /* pmask */ 0, + /* lmask */ + (CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP | + CD_MASK_NORMAL | CD_MASK_MLOOPTANGENT), }; const CustomData_MeshMasks CD_MASK_EVERYTHING = { - .vmask = (CD_MASK_MVERT | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | - CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_ORCO | - CD_MASK_CLOTH_ORCO | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_PAINT_MASK | - CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR), - .emask = (CD_MASK_MEDGE | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_BWEIGHT | - CD_MASK_CREASE | CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), - .fmask = (CD_MASK_MFACE | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | CD_MASK_MTFACE | CD_MASK_MCOL | - CD_MASK_ORIGSPACE | CD_MASK_TANGENT | CD_MASK_TESSLOOPNORMAL | CD_MASK_PREVIEW_MCOL | - CD_MASK_PROP_ALL), - .lmask = (CD_MASK_MLOOP | CD_MASK_BM_ELEM_PYPTR | CD_MASK_MDISPS | CD_MASK_NORMAL | - CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | - CD_MASK_MLOOPTANGENT | CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP | - CD_MASK_GRID_PAINT_MASK | CD_MASK_PROP_ALL), - .pmask = (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | - CD_MASK_FACEMAP | CD_MASK_FREESTYLE_FACE | CD_MASK_PROP_ALL | - CD_MASK_SCULPT_FACE_SETS), + /* vmask */ (CD_MASK_MVERT | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | + CD_MASK_MDEFORMVERT | CD_MASK_BWEIGHT | CD_MASK_MVERT_SKIN | CD_MASK_ORCO | + CD_MASK_CLOTH_ORCO | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | + CD_MASK_PAINT_MASK | CD_MASK_PROP_ALL | CD_MASK_PROP_COLOR | CD_MASK_CREASE), + /* emask */ + (CD_MASK_MEDGE | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_BWEIGHT | CD_MASK_CREASE | + CD_MASK_FREESTYLE_EDGE | CD_MASK_PROP_ALL), + /* fmask */ + (CD_MASK_MFACE | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | CD_MASK_MTFACE | CD_MASK_MCOL | + CD_MASK_ORIGSPACE | CD_MASK_TANGENT | CD_MASK_TESSLOOPNORMAL | CD_MASK_PREVIEW_MCOL | + CD_MASK_PROP_ALL), + /* pmask */ + (CD_MASK_MPOLY | CD_MASK_BM_ELEM_PYPTR | CD_MASK_ORIGINDEX | CD_MASK_NORMAL | CD_MASK_FACEMAP | + CD_MASK_FREESTYLE_FACE | CD_MASK_PROP_ALL | CD_MASK_SCULPT_FACE_SETS), + /* lmask */ + (CD_MASK_MLOOP | CD_MASK_BM_ELEM_PYPTR | CD_MASK_MDISPS | CD_MASK_NORMAL | CD_MASK_MLOOPUV | + CD_MASK_MLOOPCOL | CD_MASK_CUSTOMLOOPNORMAL | CD_MASK_MLOOPTANGENT | + CD_MASK_PREVIEW_MLOOPCOL | CD_MASK_ORIGSPACE_MLOOP | CD_MASK_GRID_PAINT_MASK | + CD_MASK_PROP_ALL), }; static const LayerTypeInfo *layerType_getInfo(int type) { if (type < 0 || type >= CD_NUMTYPES) { - return NULL; + return nullptr; } return &LAYERTYPEINFO[type]; @@ -2015,7 +2033,7 @@ static const LayerTypeInfo *layerType_getInfo(int type) static const char *layerType_getName(int type) { if (type < 0 || type >= CD_NUMTYPES) { - return NULL; + return nullptr; } return LAYERTYPENAMES[type]; @@ -2131,11 +2149,6 @@ bool CustomData_merge(const struct CustomData *source, if (flag & CD_FLAG_NOCOPY) { continue; } - if (layer->anonymous_id && - !BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) { - /* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */ - continue; - } if (!(mask & CD_TYPE_AS_MASK(type))) { continue; } @@ -2154,7 +2167,7 @@ bool CustomData_merge(const struct CustomData *source, data = layer->data; break; default: - data = NULL; + data = nullptr; break; } @@ -2176,7 +2189,7 @@ bool CustomData_merge(const struct CustomData *source, newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY); changed = true; - if (layer->anonymous_id != NULL) { + if (layer->anonymous_id != nullptr) { BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id); newlayer->anonymous_id = layer->anonymous_id; } @@ -2187,7 +2200,6 @@ bool CustomData_merge(const struct CustomData *source, return changed; } -/* NOTE: Take care of referenced layers by yourself! */ void CustomData_realloc(CustomData *data, int totelem) { for (int i = 0; i < data->totlayer; i++) { @@ -2197,7 +2209,9 @@ void CustomData_realloc(CustomData *data, int totelem) continue; } typeInfo = layerType_getInfo(layer->type); - layer->data = MEM_reallocN(layer->data, (size_t)totelem * typeInfo->size); + /* Use calloc to avoid the need to manually initialize new data in layers. + * Useful for types like #MDeformVert which contain a pointer. */ + layer->data = MEM_recallocN(layer->data, (size_t)totelem * typeInfo->size); } } @@ -2210,7 +2224,7 @@ void CustomData_copy(const struct CustomData *source, CustomData_reset(dest); if (source->external) { - dest->external = MEM_dupallocN(source->external); + dest->external = static_cast<CustomDataExternal *>(MEM_dupallocN(source->external)); } CustomData_merge(source, dest, mask, alloctype, totelem); @@ -2220,9 +2234,9 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem) { const LayerTypeInfo *typeInfo; - if (layer->anonymous_id != NULL) { + if (layer->anonymous_id != nullptr) { BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id); - layer->anonymous_id = NULL; + layer->anonymous_id = nullptr; } if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) { typeInfo = layerType_getInfo(layer->type); @@ -2241,7 +2255,7 @@ static void CustomData_external_free(CustomData *data) { if (data->external) { MEM_freeN(data->external); - data->external = NULL; + data->external = nullptr; } } @@ -2449,8 +2463,6 @@ void CustomData_set_layer_stencil(CustomData *data, int type, int n) } } -/* For using with an index from CustomData_get_active_layer_index and - * CustomData_get_render_layer_index. */ void CustomData_set_layer_active_index(CustomData *data, int type, int n) { for (int i = 0; i < data->totlayer; i++) { @@ -2509,8 +2521,8 @@ void CustomData_clear_layer_flag(struct CustomData *data, int type, int flag) static bool customData_resize(CustomData *data, int amount) { - CustomDataLayer *tmp = MEM_calloc_arrayN( - (data->maxlayer + amount), sizeof(*tmp), "CustomData->layers"); + CustomDataLayer *tmp = static_cast<CustomDataLayer *>( + MEM_calloc_arrayN((data->maxlayer + amount), sizeof(*tmp), __func__)); if (!tmp) { return false; } @@ -2534,7 +2546,7 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, { const LayerTypeInfo *typeInfo = layerType_getInfo(type); int flag = 0, index = data->totlayer; - void *newlayerdata = NULL; + void *newlayerdata = nullptr; /* Passing a layer-data to copy from with an alloctype that won't copy is * most likely a bug */ @@ -2556,7 +2568,7 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, } if (!newlayerdata) { - return NULL; + return nullptr; } } @@ -2584,7 +2596,7 @@ static CustomDataLayer *customData_add_layer__internal(CustomData *data, if (newlayerdata != layerdata) { MEM_freeN(newlayerdata); } - return NULL; + return nullptr; } } @@ -2595,6 +2607,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; @@ -2645,10 +2662,9 @@ void *CustomData_add_layer( return layer->data; } - return NULL; + return nullptr; } -/* Same as above but accepts a name. */ void *CustomData_add_layer_named(CustomData *data, int type, eCDAllocType alloctype, @@ -2664,7 +2680,7 @@ void *CustomData_add_layer_named(CustomData *data, return layer->data; } - return NULL; + return nullptr; } void *CustomData_add_layer_anonymous(struct CustomData *data, @@ -2679,8 +2695,8 @@ void *CustomData_add_layer_anonymous(struct CustomData *data, data, type, alloctype, layerdata, totelem, name); CustomData_update_typemap(data); - if (layer == NULL) { - return NULL; + if (layer == nullptr) { + return nullptr; } BKE_anonymous_attribute_id_increment_weak(anonymous_id); @@ -2793,7 +2809,7 @@ static void *customData_duplicate_referenced_layer_index(CustomData *data, const int totelem) { if (layer_index == -1) { - return NULL; + return nullptr; } CustomDataLayer *layer = &data->layers[layer_index]; @@ -2862,7 +2878,7 @@ void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data, } } BLI_assert_unreachable(); - return NULL; + return nullptr; } void CustomData_duplicate_referenced_layers(CustomData *data, int totelem) @@ -2958,7 +2974,7 @@ void CustomData_copy_data_layer(const CustomData *source, const size_t dst_offset = (size_t)dst_index * typeInfo->size; if (!count || !src_data || !dst_data) { - if (count && !(src_data == NULL && dst_data == NULL)) { + if (count && !(src_data == nullptr && dst_data == nullptr)) { CLOG_WARN(&LOG, "null data for %s type (%p --> %p), skipping", layerType_getName(source->layers[src_layer_index].type), @@ -3068,18 +3084,6 @@ void CustomData_free_elem(CustomData *data, int index, int count) #define SOURCE_BUF_SIZE 100 -/** - * Interpolate given custom data source items into a single destination one. - * - * \param src_indices: Indices of every source items to interpolate into the destination one. - * \param weights: The weight to apply to each source value individually. If NULL, they will be - * averaged. - * \param sub_weights: The weights of sub-items, only used to affect each corners of a - * tessellated face data (should always be and array of four values). - * \param count: The number of source items to interpolate. - * \param dest_index: Index of the destination item, in which to put the result of the - * interpolation. - */ void CustomData_interp(const CustomData *source, CustomData *dest, const int *src_indices, @@ -3097,15 +3101,16 @@ void CustomData_interp(const CustomData *source, /* Slow fallback in case we're interpolating a ridiculous number of elements. */ if (count > SOURCE_BUF_SIZE) { - sources = MEM_malloc_arrayN(count, sizeof(*sources), __func__); + sources = static_cast<const void **>(MEM_malloc_arrayN(count, sizeof(*sources), __func__)); } /* If no weights are given, generate default ones to produce an average result. */ float default_weights_buf[SOURCE_BUF_SIZE]; - float *default_weights = NULL; - if (weights == NULL) { + float *default_weights = nullptr; + if (weights == nullptr) { default_weights = (count > SOURCE_BUF_SIZE) ? - MEM_mallocN(sizeof(*weights) * (size_t)count, __func__) : + static_cast<float *>( + MEM_mallocN(sizeof(*weights) * (size_t)count, __func__)) : default_weights_buf; copy_vn_fl(default_weights, count, 1.0f / count); weights = default_weights; @@ -3157,18 +3162,11 @@ void CustomData_interp(const CustomData *source, if (count > SOURCE_BUF_SIZE) { MEM_freeN((void *)sources); } - if (!ELEM(default_weights, NULL, default_weights_buf)) { + if (!ELEM(default_weights, nullptr, default_weights_buf)) { MEM_freeN(default_weights); } } -/** - * Swap data inside each item, for all layers. - * This only applies to item types that may store several sub-item data - * (e.g. corner data [UVs, VCol, ...] of tessellated faces). - * - * \param corner_indices: A mapping 'new_index -> old_index' of sub-item data. - */ void CustomData_swap_corners(struct CustomData *data, int index, const int *corner_indices) { for (int i = 0; i < data->totlayer; i++) { @@ -3182,9 +3180,6 @@ void CustomData_swap_corners(struct CustomData *data, int index, const int *corn } } -/** - * Swap two items of given custom data, in all available layers. - */ void CustomData_swap(struct CustomData *data, const int index_a, const int index_b) { char buff_static[256]; @@ -3219,7 +3214,7 @@ void *CustomData_get(const CustomData *data, int index, int type) /* get the layer index of the active layer of type */ int layer_index = CustomData_get_active_layer_index(data, type); if (layer_index == -1) { - return NULL; + return nullptr; } /* get the offset of the desired element */ @@ -3235,7 +3230,7 @@ void *CustomData_get_n(const CustomData *data, int type, int index, int n) /* get the layer index of the first layer of type */ int layer_index = data->typemap[type]; if (layer_index == -1) { - return NULL; + return nullptr; } const size_t offset = (size_t)index * layerType_getInfo(type)->size; @@ -3247,7 +3242,7 @@ void *CustomData_get_layer(const CustomData *data, int type) /* get the layer index of the active layer of type */ int layer_index = CustomData_get_active_layer_index(data, type); if (layer_index == -1) { - return NULL; + return nullptr; } return data->layers[layer_index].data; @@ -3258,7 +3253,7 @@ void *CustomData_get_layer_n(const CustomData *data, int type, int n) /* get the layer index of the active layer of type */ int layer_index = CustomData_get_layer_index_n(data, type, n); if (layer_index == -1) { - return NULL; + return nullptr; } return data->layers[layer_index].data; @@ -3268,7 +3263,7 @@ void *CustomData_get_layer_named(const struct CustomData *data, int type, const { int layer_index = CustomData_get_named_layer_index(data, type, name); if (layer_index == -1) { - return NULL; + return nullptr; } return data->layers[layer_index].data; @@ -3314,7 +3309,7 @@ const char *CustomData_get_layer_name(const CustomData *data, int type, int n) { const int layer_index = CustomData_get_layer_index_n(data, type, n); - return (layer_index == -1) ? NULL : data->layers[layer_index].name; + return (layer_index == -1) ? nullptr : data->layers[layer_index].name; } void *CustomData_set_layer(const CustomData *data, int type, void *ptr) @@ -3323,7 +3318,7 @@ void *CustomData_set_layer(const CustomData *data, int type, void *ptr) int layer_index = CustomData_get_active_layer_index(data, type); if (layer_index == -1) { - return NULL; + return nullptr; } data->layers[layer_index].data = ptr; @@ -3336,7 +3331,7 @@ void *CustomData_set_layer_n(const struct CustomData *data, int type, int n, voi /* get the layer index of the first layer of type */ int layer_index = CustomData_get_layer_index_n(data, type, n); if (layer_index == -1) { - return NULL; + return nullptr; } data->layers[layer_index].data = ptr; @@ -3362,25 +3357,25 @@ void CustomData_set(const CustomData *data, int index, int type, const void *sou } /* BMesh functions */ -/* needed to convert to/from different face reps */ + void CustomData_to_bmeshpoly(CustomData *fdata, CustomData *ldata, int totloop) { for (int i = 0; i < fdata->totlayer; i++) { if (fdata->layers[i].type == CD_MTFACE) { CustomData_add_layer_named( - ldata, CD_MLOOPUV, CD_CALLOC, NULL, totloop, fdata->layers[i].name); + ldata, CD_MLOOPUV, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); } else if (fdata->layers[i].type == CD_MCOL) { CustomData_add_layer_named( - ldata, CD_MLOOPCOL, CD_CALLOC, NULL, totloop, fdata->layers[i].name); + ldata, CD_MLOOPCOL, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); } else if (fdata->layers[i].type == CD_MDISPS) { CustomData_add_layer_named( - ldata, CD_MDISPS, CD_CALLOC, NULL, totloop, fdata->layers[i].name); + ldata, CD_MDISPS, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); } else if (fdata->layers[i].type == CD_TESSLOOPNORMAL) { CustomData_add_layer_named( - ldata, CD_NORMAL, CD_CALLOC, NULL, totloop, fdata->layers[i].name); + ldata, CD_NORMAL, CD_CALLOC, nullptr, totloop, fdata->layers[i].name); } } } @@ -3392,25 +3387,27 @@ void CustomData_from_bmeshpoly(CustomData *fdata, CustomData *ldata, int total) for (int i = 0; i < ldata->totlayer; i++) { if (ldata->layers[i].type == CD_MLOOPUV) { - CustomData_add_layer_named(fdata, CD_MTFACE, CD_CALLOC, NULL, total, ldata->layers[i].name); + CustomData_add_layer_named( + fdata, CD_MTFACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); } if (ldata->layers[i].type == CD_MLOOPCOL) { - CustomData_add_layer_named(fdata, CD_MCOL, CD_CALLOC, NULL, total, ldata->layers[i].name); + CustomData_add_layer_named(fdata, CD_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); } else if (ldata->layers[i].type == CD_PREVIEW_MLOOPCOL) { CustomData_add_layer_named( - fdata, CD_PREVIEW_MCOL, CD_CALLOC, NULL, total, ldata->layers[i].name); + fdata, CD_PREVIEW_MCOL, CD_CALLOC, nullptr, total, ldata->layers[i].name); } else if (ldata->layers[i].type == CD_ORIGSPACE_MLOOP) { CustomData_add_layer_named( - fdata, CD_ORIGSPACE, CD_CALLOC, NULL, total, ldata->layers[i].name); + fdata, CD_ORIGSPACE, CD_CALLOC, nullptr, total, ldata->layers[i].name); } else if (ldata->layers[i].type == CD_NORMAL) { CustomData_add_layer_named( - fdata, CD_TESSLOOPNORMAL, CD_CALLOC, NULL, total, ldata->layers[i].name); + fdata, CD_TESSLOOPNORMAL, CD_CALLOC, nullptr, total, ldata->layers[i].name); } else if (ldata->layers[i].type == CD_TANGENT) { - CustomData_add_layer_named(fdata, CD_TANGENT, CD_CALLOC, NULL, total, ldata->layers[i].name); + CustomData_add_layer_named( + fdata, CD_TANGENT, CD_CALLOC, nullptr, total, ldata->layers[i].name); } } @@ -3418,12 +3415,6 @@ void CustomData_from_bmeshpoly(CustomData *fdata, CustomData *ldata, int total) } #ifndef NDEBUG -/** - * Debug check, used to assert when we expect layers to be in/out of sync. - * - * \param fallback: Use when there are no layers to handle, - * since callers may expect success or failure. - */ bool CustomData_from_bmeshpoly_test(CustomData *fdata, CustomData *ldata, bool fallback) { int a_num = 0, b_num = 0; @@ -3491,11 +3482,6 @@ void CustomData_bmesh_update_active_layers(CustomData *fdata, CustomData *ldata) } } -/* update active indices for active/render/clone/stencil custom data layers - * based on indices from fdata layers - * used by do_versions in readfile.c when creating pdata and ldata for pre-bmesh - * meshes and needed to preserve active/render/clone/stencil flags set in pre-bmesh files - */ void CustomData_bmesh_do_versions_update_active_layers(CustomData *fdata, CustomData *ldata) { int act; @@ -3534,7 +3520,7 @@ void CustomData_bmesh_init_pool(CustomData *data, int totelem, const char htype) int chunksize; /* Dispose old pools before calling here to avoid leaks */ - BLI_assert(data->pool == NULL); + BLI_assert(data->pool == nullptr); switch (htype) { case BM_VERT: @@ -3577,7 +3563,7 @@ bool CustomData_bmesh_merge(const CustomData *source, * the new allocation */ CustomData destold = *dest; if (destold.layers) { - destold.layers = MEM_dupallocN(destold.layers); + destold.layers = static_cast<CustomDataLayer *>(MEM_dupallocN(destold.layers)); } if (CustomData_merge(source, dest, mask, alloctype, 0) == false) { @@ -3613,7 +3599,7 @@ bool CustomData_bmesh_merge(const CustomData *source, break; } - dest->pool = NULL; + dest->pool = nullptr; CustomData_bmesh_init_pool(dest, totelem, htype); if (iter_type != BM_LOOPS_OF_FACE) { @@ -3621,7 +3607,7 @@ bool CustomData_bmesh_merge(const CustomData *source, BMIter iter; /* Ensure all current elements follow new customdata layout. */ BM_ITER_MESH (h, &iter, bm, iter_type) { - void *tmp = NULL; + void *tmp = nullptr; CustomData_bmesh_copy_data(&destold, dest, h->data, &tmp); CustomData_bmesh_free_block(&destold, &h->data); h->data = tmp; @@ -3636,7 +3622,7 @@ bool CustomData_bmesh_merge(const CustomData *source, /* Ensure all current elements follow new customdata layout. */ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - void *tmp = NULL; + void *tmp = nullptr; CustomData_bmesh_copy_data(&destold, dest, l->head.data, &tmp); CustomData_bmesh_free_block(&destold, &l->head.data); l->head.data = tmp; @@ -3655,7 +3641,7 @@ bool CustomData_bmesh_merge(const CustomData *source, void CustomData_bmesh_free_block(CustomData *data, void **block) { - if (*block == NULL) { + if (*block == nullptr) { return; } @@ -3674,15 +3660,12 @@ void CustomData_bmesh_free_block(CustomData *data, void **block) BLI_mempool_free(data->pool, *block); } - *block = NULL; + *block = nullptr; } -/** - * Same as #CustomData_bmesh_free_block but zero the memory rather than freeing. - */ void CustomData_bmesh_free_block_data(CustomData *data, void *block) { - if (block == NULL) { + if (block == nullptr) { return; } for (int i = 0; i < data->totlayer; i++) { @@ -3709,18 +3692,15 @@ static void CustomData_bmesh_alloc_block(CustomData *data, void **block) *block = BLI_mempool_alloc(data->pool); } else { - *block = NULL; + *block = nullptr; } } -/** - * A selective version of #CustomData_bmesh_free_block_data. - */ void CustomData_bmesh_free_block_data_exclude_by_type(CustomData *data, void *block, const CustomDataMask mask_exclude) { - if (block == NULL) { + if (block == nullptr) { return; } for (int i = 0; i < data->totlayer; i++) { @@ -3752,7 +3732,7 @@ static void CustomData_bmesh_set_default_n(CustomData *data, void **block, int n void CustomData_bmesh_set_default(CustomData *data, void **block) { - if (*block == NULL) { + if (*block == nullptr) { CustomData_bmesh_alloc_block(data, block); } @@ -3771,7 +3751,7 @@ void CustomData_bmesh_copy_data_exclude_by_type(const CustomData *source, * would cause too much duplicate code, so add a check instead. */ const bool no_mask = (mask_exclude == 0); - if (*dest_block == NULL) { + if (*dest_block == nullptr) { CustomData_bmesh_alloc_block(dest, dest_block); if (*dest_block) { memset(*dest_block, 0, dest->totsize); @@ -3832,15 +3812,12 @@ void CustomData_bmesh_copy_data(const CustomData *source, CustomData_bmesh_copy_data_exclude_by_type(source, dest, src_block, dest_block, 0); } -/* BMesh Custom Data Functions. - * Should replace edit-mesh ones with these as well, due to more efficient memory alloc. - */ void *CustomData_bmesh_get(const CustomData *data, void *block, int type) { /* get the layer index of the first layer of type */ int layer_index = CustomData_get_active_layer_index(data, type); if (layer_index == -1) { - return NULL; + return nullptr; } return POINTER_OFFSET(block, data->layers[layer_index].offset); @@ -3851,17 +3828,16 @@ void *CustomData_bmesh_get_n(const CustomData *data, void *block, int type, int /* get the layer index of the first layer of type */ int layer_index = CustomData_get_layer_index(data, type); if (layer_index == -1) { - return NULL; + return nullptr; } return POINTER_OFFSET(block, data->layers[layer_index + n].offset); } -/* 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) { - return NULL; + return nullptr; } return POINTER_OFFSET(block, data->layers[n].offset); @@ -3902,7 +3878,6 @@ bool CustomData_has_math(const struct CustomData *data) return false; } -/* a non bmesh version would have to check layer->data */ bool CustomData_bmesh_has_free(const struct CustomData *data) { for (int i = 0; i < data->totlayer; i++) { @@ -3938,8 +3913,6 @@ bool CustomData_has_referenced(const struct CustomData *data) return false; } -/* copies the "value" (e.g. mloopuv uv or mloopcol colors) from one block to - * another, while not overwriting anything else (e.g. flags). */ void CustomData_data_copy_value(int type, const void *source, void *dest) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); @@ -3956,8 +3929,6 @@ void CustomData_data_copy_value(int type, const void *source, void *dest) } } -/* Mixes the "value" (e.g. mloopuv uv or mloopcol colors) from one block into - * another, while not overwriting anything else (e.g. flags). */ void CustomData_data_mix_value( int type, const void *source, void *dest, const int mixmode, const float mixfactor) { @@ -4074,10 +4045,6 @@ void CustomData_bmesh_set_layer_n(CustomData *data, void *block, int n, const vo } } -/** - * \note src_blocks_ofs & dst_block_ofs - * must be pointers to the data, offset by layer->offset already. - */ void CustomData_bmesh_interp_n(CustomData *data, const void **src_blocks_ofs, const float *weights, @@ -4086,7 +4053,7 @@ void CustomData_bmesh_interp_n(CustomData *data, void *dst_block_ofs, int n) { - BLI_assert(weights != NULL); + BLI_assert(weights != nullptr); BLI_assert(count > 0); CustomDataLayer *layer = &data->layers[n]; @@ -4111,15 +4078,15 @@ void CustomData_bmesh_interp(CustomData *data, /* Slow fallback in case we're interpolating a ridiculous number of elements. */ if (count > SOURCE_BUF_SIZE) { - sources = MEM_malloc_arrayN(count, sizeof(*sources), __func__); + sources = (const void **)MEM_malloc_arrayN(count, sizeof(*sources), __func__); } /* If no weights are given, generate default ones to produce an average result. */ float default_weights_buf[SOURCE_BUF_SIZE]; - float *default_weights = NULL; - if (weights == NULL) { + float *default_weights = nullptr; + if (weights == nullptr) { default_weights = (count > SOURCE_BUF_SIZE) ? - MEM_mallocN(sizeof(*weights) * (size_t)count, __func__) : + (float *)MEM_mallocN(sizeof(*weights) * (size_t)count, __func__) : default_weights_buf; copy_vn_fl(default_weights, count, 1.0f / count); weights = default_weights; @@ -4141,23 +4108,18 @@ void CustomData_bmesh_interp(CustomData *data, if (count > SOURCE_BUF_SIZE) { MEM_freeN((void *)sources); } - if (!ELEM(default_weights, NULL, default_weights_buf)) { + if (!ELEM(default_weights, nullptr, default_weights_buf)) { MEM_freeN(default_weights); } } -/** - * \param use_default_init: initializes data which can't be copied, - * typically you'll want to use this if the BM_xxx create function - * is called with BM_CREATE_SKIP_CD flag - */ void CustomData_to_bmesh_block(const CustomData *source, CustomData *dest, int src_index, void **dest_block, bool use_default_init) { - if (*dest_block == NULL) { + if (*dest_block == nullptr) { CustomData_bmesh_alloc_block(dest, dest_block); } @@ -4265,25 +4227,6 @@ void CustomData_file_write_info(int type, const char **r_struct_name, int *r_str *r_struct_num = typeInfo->structnum; } -/** - * Prepare given custom data for file writing. - * - * \param data: the customdata to tweak for .blend file writing (modified in place). - * \param r_write_layers: contains a reduced set of layers to be written to file, - * use it with writestruct_at_address() - * (caller must free it if != \a write_layers_buff). - * - * \param write_layers_buff: an optional buffer for r_write_layers (to avoid allocating it). - * \param write_layers_size: the size of pre-allocated \a write_layer_buff. - * - * \warning After this func has ran, given custom data is no more valid from Blender PoV - * (its totlayer is invalid). This func shall always be called with localized data - * (as it is in write_meshes()). - * - * \note data->typemap is not updated here, since it is always rebuilt on file read anyway. - * This means written typemap does not match written layers (as returned by \a r_write_layers). - * Trivial to fix is ever needed. - */ void CustomData_blend_write_prepare(CustomData *data, CustomDataLayer **r_write_layers, CustomDataLayer *write_layers_buff, @@ -4298,22 +4241,22 @@ void CustomData_blend_write_prepare(CustomData *data, for (i = 0, j = 0; i < totlayer; i++) { CustomDataLayer *layer = &data->layers[i]; /* Layers with this flag set are not written to file. */ - if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) { + if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != nullptr) { data->totlayer--; // CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name); } else { if (UNLIKELY((size_t)j >= write_layers_size)) { if (write_layers == write_layers_buff) { - write_layers = MEM_malloc_arrayN( + write_layers = (CustomDataLayer *)MEM_malloc_arrayN( (write_layers_size + chunk_size), sizeof(*write_layers), __func__); if (write_layers_buff) { memcpy(write_layers, write_layers_buff, sizeof(*write_layers) * write_layers_size); } } else { - write_layers = MEM_reallocN(write_layers, - sizeof(*write_layers) * (write_layers_size + chunk_size)); + write_layers = (CustomDataLayer *)MEM_reallocN( + write_layers, sizeof(*write_layers) * (write_layers_size + chunk_size)); } write_layers_size += chunk_size; } @@ -4337,39 +4280,28 @@ const char *CustomData_layertype_name(int type) return layerType_getName(type); } -/** - * Can only ever be one of these. - */ bool CustomData_layertype_is_singleton(int type) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); - return typeInfo->defaultname == NULL; + return typeInfo->defaultname == nullptr; } -/** - * Has dynamically allocated members. - * This is useful to know if operations such as #memcmp are - * valid when comparing data from two layers. - */ bool CustomData_layertype_is_dynamic(int type) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); - return (typeInfo->free != NULL); + return (typeInfo->free != nullptr); } -/** - * \return Maximum number of layers of given \a type, -1 means 'no limit'. - */ int CustomData_layertype_layers_max(const int type) { const LayerTypeInfo *typeInfo = layerType_getInfo(type); /* Same test as for singleton above. */ - if (typeInfo->defaultname == NULL) { + if (typeInfo->defaultname == nullptr) { return 1; } - if (typeInfo->layers_max == NULL) { + if (typeInfo->layers_max == nullptr) { return -1; } @@ -4399,13 +4331,15 @@ static bool cd_layer_find_dupe(CustomData *data, const char *name, int type, int return false; } +struct CustomDataUniqueCheckData { + CustomData *data; + int type; + int index; +}; + static bool customdata_unique_check(void *arg, const char *name) { - struct { - CustomData *data; - int type; - int index; - } *data_arg = arg; + CustomDataUniqueCheckData *data_arg = static_cast<CustomDataUniqueCheckData *>(arg); return cd_layer_find_dupe(data_arg->data, name, data_arg->type, data_arg->index); } @@ -4414,14 +4348,7 @@ void CustomData_set_layer_unique_name(CustomData *data, int index) CustomDataLayer *nlayer = &data->layers[index]; const LayerTypeInfo *typeInfo = layerType_getInfo(nlayer->type); - struct { - CustomData *data; - int type; - int index; - } data_arg; - data_arg.data = data; - data_arg.type = nlayer->type; - data_arg.index = index; + CustomDataUniqueCheckData data_arg{data, nlayer->type, index}; if (!typeInfo->defaultname) { return; @@ -4434,7 +4361,7 @@ void CustomData_set_layer_unique_name(CustomData *data, int index) } BLI_uniquename_cb( - customdata_unique_check, &data_arg, NULL, '.', nlayer->name, sizeof(nlayer->name)); + customdata_unique_check, &data_arg, nullptr, '.', nlayer->name, sizeof(nlayer->name)); } void CustomData_validate_layer_name(const CustomData *data, @@ -4486,7 +4413,12 @@ bool CustomData_verify_versions(struct CustomData *data, int index) /* 0 structnum is used in writing code to tag layer types that should not be written. */ else if (typeInfo->structnum == 0 && /* XXX Not sure why those three are exception, maybe that should be fixed? */ - !ELEM(layer->type, CD_PAINT_MASK, CD_FACEMAP, CD_MTEXPOLY, CD_SCULPT_FACE_SETS)) { + !ELEM(layer->type, + CD_PAINT_MASK, + CD_FACEMAP, + CD_MTEXPOLY, + CD_SCULPT_FACE_SETS, + CD_CREASE)) { keeplayer = false; CLOG_WARN(&LOG, ".blend file read: removing a data layer that should not have been written"); } @@ -4502,17 +4434,11 @@ bool CustomData_verify_versions(struct CustomData *data, int index) return keeplayer; } -/** - * Validate and fix data of \a layer, - * if possible (needs relevant callback in layer's type to be defined). - * - * \return True if some errors were found. - */ bool CustomData_layer_validate(CustomDataLayer *layer, const uint totitems, const bool do_fixes) { const LayerTypeInfo *typeInfo = layerType_getInfo(layer->type); - if (typeInfo->validate != NULL) { + if (typeInfo->validate != nullptr) { return typeInfo->validate(layer->data, totitems, do_fixes); } @@ -4764,7 +4690,7 @@ void CustomData_external_add( } if (!external) { - external = MEM_callocN(sizeof(CustomDataExternal), "CustomDataExternal"); + external = MEM_cnew<CustomDataExternal>(__func__); data->external = external; } BLI_strncpy(external->filename, filename, sizeof(external->filename)); @@ -4863,7 +4789,7 @@ static void customdata_data_transfer_interp_generic(const CustomDataTransferLaye const int count, const float mix_factor) { - BLI_assert(weights != NULL); + BLI_assert(weights != nullptr); BLI_assert(count > 0); /* Fake interpolation, we actually copy highest weighted source to dest. @@ -4878,8 +4804,8 @@ static void customdata_data_transfer_interp_generic(const CustomDataTransferLaye size_t data_size; const uint64_t data_flag = laymap->data_flag; - cd_interp interp_cd = NULL; - cd_copy copy_cd = NULL; + cd_interp interp_cd = nullptr; + cd_copy copy_cd = nullptr; if (!sources) { /* Not supported here, abort. */ @@ -4933,7 +4859,7 @@ static void customdata_data_transfer_interp_generic(const CustomDataTransferLaye BLI_assert(best_src_idx >= 0); if (interp_cd) { - interp_cd(sources, weights, NULL, count, tmp_dst); + interp_cd(sources, weights, nullptr, count, tmp_dst); } else if (data_flag) { copy_bit_flag(tmp_dst, sources[best_src_idx], data_size, data_flag); @@ -4971,7 +4897,6 @@ static void customdata_data_transfer_interp_generic(const CustomDataTransferLaye MEM_freeN(tmp_dst); } -/* Normals are special, we need to take care of source & destination spaces... */ void customdata_data_transfer_interp_normal_normals(const CustomDataTransferLayerMap *laymap, void *data_dst, const void **sources, @@ -4979,13 +4904,13 @@ void customdata_data_transfer_interp_normal_normals(const CustomDataTransferLaye const int count, const float mix_factor) { - BLI_assert(weights != NULL); + BLI_assert(weights != nullptr); BLI_assert(count > 0); const int data_type = laymap->data_type; const int mix_mode = laymap->mix_mode; - SpaceTransform *space_transform = laymap->interp_data; + SpaceTransform *space_transform = static_cast<SpaceTransform *>(laymap->interp_data); const LayerTypeInfo *type_info = layerType_getInfo(data_type); cd_interp interp_cd = type_info->interp; @@ -4999,7 +4924,7 @@ void customdata_data_transfer_interp_normal_normals(const CustomDataTransferLaye return; } - interp_cd(sources, weights, NULL, count, tmp_dst); + interp_cd(sources, weights, nullptr, count, tmp_dst); if (space_transform) { /* tmp_dst is in source space so far, bring it back in destination space. */ BLI_space_transform_invert_normal(space_transform, tmp_dst); @@ -5022,18 +4947,19 @@ void CustomData_data_transfer(const MeshPairRemap *me_remap, size_t data_size; size_t data_offset; - cd_datatransfer_interp interp = NULL; + cd_datatransfer_interp interp = nullptr; size_t tmp_buff_size = 32; - const void **tmp_data_src = NULL; + const void **tmp_data_src = nullptr; - /* 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; } if (data_src) { - tmp_data_src = MEM_malloc_arrayN(tmp_buff_size, sizeof(*tmp_data_src), __func__); + tmp_data_src = (const void **)MEM_malloc_arrayN( + tmp_buff_size, sizeof(*tmp_data_src), __func__); } if (data_type & CD_FAKE) { @@ -5065,7 +4991,8 @@ void CustomData_data_transfer(const MeshPairRemap *me_remap, if (tmp_data_src) { if (UNLIKELY(sources_num > tmp_buff_size)) { tmp_buff_size = (size_t)sources_num; - tmp_data_src = MEM_reallocN((void *)tmp_data_src, sizeof(*tmp_data_src) * tmp_buff_size); + tmp_data_src = (const void **)MEM_reallocN((void *)tmp_data_src, + sizeof(*tmp_data_src) * tmp_buff_size); } for (int j = 0; j < sources_num; j++) { @@ -5118,9 +5045,6 @@ static void write_grid_paint_mask(BlendWriter *writer, int count, GridPaintMask } } -/** - * \param layers: The layers argument assigned by #CustomData_blend_write_prepare. - */ void CustomData_blend_write(BlendWriter *writer, CustomData *data, CustomDataLayer *layers, @@ -5140,28 +5064,33 @@ void CustomData_blend_write(BlendWriter *writer, if (layer->type == CD_MDEFORMVERT) { /* layer types that allocate own memory need special handling */ - BKE_defvert_blend_write(writer, count, layer->data); + BKE_defvert_blend_write(writer, count, static_cast<struct MDeformVert *>(layer->data)); } else if (layer->type == CD_MDISPS) { - write_mdisps(writer, count, layer->data, layer->flag & CD_FLAG_EXTERNAL); + write_mdisps( + writer, count, static_cast<MDisps *>(layer->data), layer->flag & CD_FLAG_EXTERNAL); } else if (layer->type == CD_PAINT_MASK) { - const float *layer_data = layer->data; + const float *layer_data = static_cast<const float *>(layer->data); BLO_write_raw(writer, sizeof(*layer_data) * count, layer_data); } else if (layer->type == CD_SCULPT_FACE_SETS) { - const float *layer_data = layer->data; + const float *layer_data = static_cast<const float *>(layer->data); BLO_write_raw(writer, sizeof(*layer_data) * count, layer_data); } else if (layer->type == CD_GRID_PAINT_MASK) { - write_grid_paint_mask(writer, count, layer->data); + write_grid_paint_mask(writer, count, static_cast<GridPaintMask *>(layer->data)); } else if (layer->type == CD_FACEMAP) { - const int *layer_data = layer->data; + const int *layer_data = static_cast<const int *>(layer->data); BLO_write_raw(writer, sizeof(*layer_data) * count, layer_data); } else if (layer->type == CD_PROP_BOOL) { - const bool *layer_data = layer->data; + const bool *layer_data = static_cast<const bool *>(layer->data); + BLO_write_raw(writer, sizeof(*layer_data) * count, layer_data); + } + else if (layer->type == CD_CREASE) { + const float *layer_data = static_cast<const float *>(layer->data); BLO_write_raw(writer, sizeof(*layer_data) * count, layer_data); } else { @@ -5234,7 +5163,7 @@ void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count) /* Annoying workaround for bug T31079 loading legacy files with * no polygons _but_ have stale custom-data. */ - if (UNLIKELY(count == 0 && data->layers == NULL && data->totlayer != 0)) { + if (UNLIKELY(count == 0 && data->layers == nullptr && data->totlayer != 0)) { CustomData_reset(data); return; } @@ -5253,7 +5182,7 @@ void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count) if (CustomData_verify_versions(data, i)) { BLO_read_data_address(reader, &layer->data); - if (layer->data == NULL && count > 0 && layer->type == CD_PROP_BOOL) { + if (layer->data == nullptr && count > 0 && layer->type == CD_PROP_BOOL) { /* Usually this should never happen, except when a custom data layer has not been written * to a file correctly. */ CLOG_WARN(&LOG, "Reallocating custom data layer that was not saved correctly."); @@ -5264,10 +5193,11 @@ void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count) } } if (layer->type == CD_MDISPS) { - blend_read_mdisps(reader, count, layer->data, layer->flag & CD_FLAG_EXTERNAL); + blend_read_mdisps( + reader, count, static_cast<MDisps *>(layer->data), layer->flag & CD_FLAG_EXTERNAL); } else if (layer->type == CD_GRID_PAINT_MASK) { - blend_read_paint_mask(reader, count, layer->data); + blend_read_paint_mask(reader, count, static_cast<GridPaintMask *>(layer->data)); } i++; } @@ -5275,3 +5205,33 @@ void CustomData_blend_read(BlendDataReader *reader, CustomData *data, int count) CustomData_update_typemap(data); } + +#ifndef NDEBUG + +void CustomData_debug_info_from_layers(const CustomData *data, const char *indent, DynStr *dynstr) +{ + for (int type = 0; type < CD_NUMTYPES; type++) { + if (CustomData_has_layer(data, type)) { + /* 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(data, type); + const int pt_size = pt ? (int)(MEM_allocN_len(pt) / size) : 0; + const char *structname; + int structnum; + CustomData_file_write_info(type, &structname, &structnum); + BLI_dynstr_appendf( + dynstr, + "%sdict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n", + indent, + name, + structname, + type, + (const void *)pt, + size, + pt_size); + } + } +} + +#endif /* NDEBUG */ diff --git a/source/blender/blenkernel/intern/data_transfer.c b/source/blender/blenkernel/intern/data_transfer.c index b83621e8b79..0ad7efb6347 100644 --- a/source/blender/blenkernel/intern/data_transfer.c +++ b/source/blender/blenkernel/intern/data_transfer.c @@ -93,10 +93,6 @@ void BKE_object_data_transfer_dttypes_to_cdmask(const int dtdata_types, } } -/** - * Check what can do each layer type - * (if it is actually handled by transfer-data, if it supports advanced mixing. - */ bool BKE_object_data_transfer_get_dttypes_capacity(const int dtdata_types, bool *r_advanced_mixing, bool *r_threshold) @@ -277,7 +273,6 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src, const int num_polys_dst = me_dst->totpoly; MLoop *loops_dst = me_dst->mloop; const int num_loops_dst = me_dst->totloop; - CustomData *pdata_dst = &me_dst->pdata; CustomData *ldata_dst = &me_dst->ldata; const bool use_split_nors_dst = (me_dst->flag & ME_AUTOSMOOTH) != 0; @@ -288,26 +283,9 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src, BLI_assert(CustomData_get_layer(&me_src->pdata, CD_NORMAL) != NULL); (void)me_src; - float(*poly_nors_dst)[3]; float(*loop_nors_dst)[3]; short(*custom_nors_dst)[2] = CustomData_get_layer(ldata_dst, CD_CUSTOMLOOPNORMAL); - /* Cache poly nors into a temp CDLayer. */ - poly_nors_dst = CustomData_get_layer(pdata_dst, CD_NORMAL); - const bool do_poly_nors_dst = (poly_nors_dst == NULL); - if (do_poly_nors_dst) { - poly_nors_dst = CustomData_add_layer(pdata_dst, CD_NORMAL, CD_CALLOC, NULL, num_polys_dst); - CustomData_set_layer_flag(pdata_dst, CD_NORMAL, CD_FLAG_TEMPORARY); - } - if (dirty_nors_dst || do_poly_nors_dst) { - BKE_mesh_calc_normals_poly(verts_dst, - num_verts_dst, - loops_dst, - num_loops_dst, - polys_dst, - num_polys_dst, - poly_nors_dst); - } /* Cache loop nors into a temp CDLayer. */ loop_nors_dst = CustomData_get_layer(ldata_dst, CD_NORMAL); const bool do_loop_nors_dst = (loop_nors_dst == NULL); @@ -317,6 +295,7 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src, } if (dirty_nors_dst || do_loop_nors_dst) { BKE_mesh_normals_loop_split(verts_dst, + BKE_mesh_vertex_normals_ensure(me_dst), num_verts_dst, edges_dst, num_edges_dst, @@ -324,7 +303,7 @@ static void data_transfer_dtdata_type_preprocess(Mesh *me_src, loop_nors_dst, num_loops_dst, polys_dst, - (const float(*)[3])poly_nors_dst, + BKE_mesh_poly_normals_ensure(me_dst), num_polys_dst, use_split_nors_dst, split_angle_dst, @@ -372,6 +351,7 @@ static void data_transfer_dtdata_type_postprocess(Object *UNUSED(ob_src), /* Note loop_nors_dst contains our custom normals as transferred from source... */ BKE_mesh_normals_loop_custom_set(verts_dst, + BKE_mesh_vertex_normals_ensure(me_dst), num_verts_dst, edges_dst, num_edges_dst, @@ -1232,12 +1212,6 @@ static bool data_transfer_layersmapping_generate(ListBase *r_map, return false; } -/** - * Transfer data *layout* of selected types from source to destination object. - * By default, it only creates new data layers if needed on \a ob_dst. - * If \a use_delete is true, it will also delete data layers on \a ob_dst that do not match those - * from \a ob_src, to get (as much as possible) exact copy of source data layout. - */ void BKE_object_data_transfer_layout(struct Depsgraph *depsgraph, Scene *scene, Object *ob_src, @@ -1661,7 +1635,6 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, const int num_polys_dst = me_dst->totpoly; MLoop *loops_dst = me_dst->mloop; const int num_loops_dst = me_dst->totloop; - CustomData *pdata_dst = &me_dst->pdata; CustomData *ldata_dst = &me_dst->ldata; MeshRemapIslandsCalc island_callback = data_transfer_get_loop_islands_generator(cddata_type); @@ -1695,6 +1668,7 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, space_transform, max_distance, ray_radius, + me_dst, verts_dst, num_verts_dst, edges_dst, @@ -1704,7 +1678,6 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, polys_dst, num_polys_dst, ldata_dst, - pdata_dst, (me_dst->flag & ME_AUTOSMOOTH) != 0, me_dst->smoothresh, dirty_nors_dst, @@ -1755,7 +1728,6 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, const int num_polys_dst = me_dst->totpoly; MLoop *loops_dst = me_dst->mloop; const int num_loops_dst = me_dst->totloop; - CustomData *pdata_dst = &me_dst->pdata; if (!geom_map_init[PDATA]) { const int num_polys_src = me_src->totpoly; @@ -1786,14 +1758,11 @@ bool BKE_object_data_transfer_ex(struct Depsgraph *depsgraph, space_transform, max_distance, ray_radius, + me_dst, verts_dst, - num_verts_dst, loops_dst, - num_loops_dst, polys_dst, num_polys_dst, - pdata_dst, - dirty_nors_dst, me_src, &geom_map[PDATA]); geom_map_init[PDATA] = true; diff --git a/source/blender/blenkernel/intern/data_transfer_intern.h b/source/blender/blenkernel/intern/data_transfer_intern.h index c5d7dd42cb8..b5b3db31fbf 100644 --- a/source/blender/blenkernel/intern/data_transfer_intern.h +++ b/source/blender/blenkernel/intern/data_transfer_intern.h @@ -25,52 +25,61 @@ #include "BKE_customdata.h" /* For cd_datatransfer_interp */ +#ifdef __cplusplus +extern "C" { +#endif + struct CustomData; struct CustomDataTransferLayerMap; struct ListBase; -float data_transfer_interp_float_do(const int mix_mode, - const float val_dst, - const float val_src, - const float mix_factor); +float data_transfer_interp_float_do(int mix_mode, float val_dst, float val_src, float mix_factor); void data_transfer_layersmapping_add_item(struct ListBase *r_map, - const int data_type, - const int mix_mode, - const float mix_factor, + int data_type, + int mix_mode, + float mix_factor, const float *mix_weights, const void *data_src, void *data_dst, - const int data_src_n, - const int data_dst_n, - const size_t elem_size, - const size_t data_size, - const size_t data_offset, - const uint64_t data_flag, + int data_src_n, + int data_dst_n, + size_t elem_size, + size_t data_size, + size_t data_offset, + uint64_t data_flag, cd_datatransfer_interp interp, void *interp_data); /* Type-specific. */ bool data_transfer_layersmapping_vgroups(struct ListBase *r_map, - const int mix_mode, - const float mix_factor, + int mix_mode, + float mix_factor, const float *mix_weights, - const int num_elem_dst, - const bool use_create, - const bool use_delete, + int num_elem_dst, + bool use_create, + bool use_delete, struct Object *ob_src, struct Object *ob_dst, struct CustomData *cd_src, struct CustomData *cd_dst, - const bool use_dupref_dst, - const int fromlayers, - const int tolayers); + bool use_dupref_dst, + int fromlayers, + int tolayers); /* Defined in customdata.c */ + +/** + * Normals are special, we need to take care of source & destination spaces. + */ void customdata_data_transfer_interp_normal_normals(const CustomDataTransferLayerMap *laymap, void *data_dst, const void **sources, const float *weights, - const int count, - const float mix_factor); + int count, + float mix_factor); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/blenkernel/intern/deform.c b/source/blender/blenkernel/intern/deform.c index 13222747a52..6b429a146d4 100644 --- a/source/blender/blenkernel/intern/deform.c +++ b/source/blender/blenkernel/intern/deform.c @@ -107,11 +107,6 @@ bDeformGroup *BKE_defgroup_duplicate(const bDeformGroup *ingroup) return outgroup; } -/** - * Overwrite weights filtered by vgroup_subset. - * - do nothing if neither are set. - * - add destination weight if needed - */ void BKE_defvert_copy_subset(MDeformVert *dvert_dst, const MDeformVert *dvert_src, const bool *vgroup_subset, @@ -125,11 +120,6 @@ void BKE_defvert_copy_subset(MDeformVert *dvert_dst, } } -/** - * Overwrite weights filtered by vgroup_subset and with mirroring specified by the flip map - * - do nothing if neither are set. - * - add destination weight if needed - */ void BKE_defvert_mirror_subset(MDeformVert *dvert_dst, const MDeformVert *dvert_src, const bool *vgroup_subset, @@ -168,11 +158,6 @@ void BKE_defvert_copy(MDeformVert *dvert_dst, const MDeformVert *dvert_src) } } -/** - * Copy an index from one dvert to another. - * - do nothing if neither are set. - * - add destination weight if needed. - */ void BKE_defvert_copy_index(MDeformVert *dvert_dst, const int defgroup_dst, const MDeformVert *dvert_src, @@ -197,10 +182,6 @@ void BKE_defvert_copy_index(MDeformVert *dvert_dst, } } -/** - * Only sync over matching weights, don't add or remove groups - * warning, loop within loop. - */ void BKE_defvert_sync(MDeformVert *dvert_dst, const MDeformVert *dvert_src, const bool use_ensure) { if (dvert_src->totweight && dvert_dst->totweight) { @@ -221,9 +202,6 @@ void BKE_defvert_sync(MDeformVert *dvert_dst, const MDeformVert *dvert_src, cons } } -/** - * be sure all flip_map values are valid - */ void BKE_defvert_sync_mapped(MDeformVert *dvert_dst, const MDeformVert *dvert_src, const int *flip_map, @@ -250,9 +228,6 @@ void BKE_defvert_sync_mapped(MDeformVert *dvert_dst, } } -/** - * be sure all flip_map values are valid - */ void BKE_defvert_remap(MDeformVert *dvert, const int *map, const int map_len) { MDeformWeight *dw = dvert->dw; @@ -265,9 +240,6 @@ void BKE_defvert_remap(MDeformVert *dvert, const int *map, const int map_len) } } -/** - * Same as #BKE_defvert_normalize but takes a bool array. - */ void BKE_defvert_normalize_subset(MDeformVert *dvert, const bool *vgroup_subset, const int vgroup_tot) @@ -334,9 +306,6 @@ void BKE_defvert_normalize(MDeformVert *dvert) } } -/** - * Same as BKE_defvert_normalize() if the locked vgroup is not a member of the subset - */ void BKE_defvert_normalize_lock_single(MDeformVert *dvert, const bool *vgroup_subset, const int vgroup_tot, @@ -391,9 +360,6 @@ void BKE_defvert_normalize_lock_single(MDeformVert *dvert, } } -/** - * Same as BKE_defvert_normalize() if no locked vgroup is a member of the subset - */ void BKE_defvert_normalize_lock_map(MDeformVert *dvert, const bool *vgroup_subset, const int vgroup_tot, @@ -557,11 +523,35 @@ bDeformGroup *BKE_object_defgroup_find_name(const Object *ob, const char *name) int BKE_id_defgroup_name_index(const ID *id, const char *name) { - if (name == NULL || name[0] == '\0') { + int index; + if (!BKE_id_defgroup_name_find(id, name, &index, NULL)) { return -1; } + return index; +} + +bool BKE_id_defgroup_name_find(const struct ID *id, + const char *name, + int *r_index, + struct bDeformGroup **r_group) +{ + if (name == NULL || name[0] == '\0') { + return false; + } const ListBase *defbase = BKE_id_defgroup_list_get(id); - return BLI_findstringindex(defbase, name, offsetof(bDeformGroup, name)); + int index; + LISTBASE_FOREACH_INDEX (bDeformGroup *, group, defbase, index) { + if (STREQ(name, group->name)) { + if (r_index != NULL) { + *r_index = index; + } + if (r_group != NULL) { + *r_group = group; + } + return true; + } + } + return false; } const ListBase *BKE_object_defgroup_list(const Object *ob) @@ -586,17 +576,11 @@ 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. */ @@ -604,9 +588,6 @@ void BKE_object_defgroup_active_index_set(Object *ob, const int new_index) *index = new_index; } -/** - * \note caller must free. - */ int *BKE_object_defgroup_flip_map(const Object *ob, int *flip_map_len, const bool use_default) { const ListBase *defbase = BKE_object_defgroup_list(ob); @@ -646,9 +627,6 @@ int *BKE_object_defgroup_flip_map(const Object *ob, int *flip_map_len, const boo return map; } -/** - * \note caller must free. - */ int *BKE_object_defgroup_flip_map_single(const Object *ob, int *flip_map_len, const bool use_default, @@ -745,13 +723,6 @@ float BKE_defvert_find_weight(const struct MDeformVert *dvert, const int defgrou return dw ? dw->weight : 0.0f; } -/** - * Take care with this the rationale is: - * - if the object has no vertex group. act like vertex group isn't set and return 1.0, - * - if the vertex group exists but the 'defgroup' isn't found on this vertex, _still_ return 0.0 - * - * This is a bit confusing, just saves some checks from the caller. - */ float BKE_defvert_array_find_weight_safe(const struct MDeformVert *dvert, const int index, const int defgroup) @@ -790,11 +761,6 @@ MDeformWeight *BKE_defvert_find_index(const MDeformVert *dvert, const int defgro return NULL; } -/** - * Ensures that mv has a deform weight entry for the specified defweight group. - * - * \note this function is mirrored in editmesh_tools.c, for use for editvertices. - */ MDeformWeight *BKE_defvert_ensure_index(MDeformVert *dvert, const int defgroup) { MDeformWeight *dw_new; @@ -826,15 +792,10 @@ MDeformWeight *BKE_defvert_ensure_index(MDeformVert *dvert, const int defgroup) return dw_new; } -/* TODO: merge with code above! */ - -/** - * Adds the given vertex to the specified vertex group, with given weight. - * - * \warning this does NOT check for existing, assume caller already knows its not there. - */ void BKE_defvert_add_index_notest(MDeformVert *dvert, int defgroup, const float weight) { + /* TODO: merge with #BKE_defvert_ensure_index! */ + MDeformWeight *dw_new; /* do this check always, this function is used to check for it */ @@ -856,11 +817,6 @@ void BKE_defvert_add_index_notest(MDeformVert *dvert, int defgroup, const float dvert->totweight++; } -/** - * Removes the given vertex from the vertex group. - * - * \warning This function frees the given MDeformWeight, do not use it afterward! - */ void BKE_defvert_remove_group(MDeformVert *dvert, MDeformWeight *dw) { if (dvert && dw) { @@ -899,10 +855,6 @@ void BKE_defvert_clear(MDeformVert *dvert) dvert->totweight = 0; } -/** - * \return The first group index shared by both deform verts - * or -1 if none are found. - */ int BKE_defvert_find_shared(const MDeformVert *dvert_a, const MDeformVert *dvert_b) { if (dvert_a->totweight && dvert_b->totweight) { @@ -919,9 +871,6 @@ int BKE_defvert_find_shared(const MDeformVert *dvert_a, const MDeformVert *dvert return -1; } -/** - * return true if has no weights - */ bool BKE_defvert_is_weight_zero(const struct MDeformVert *dvert, const int defgroup_tot) { MDeformWeight *dw = dvert->dw; @@ -936,9 +885,6 @@ bool BKE_defvert_is_weight_zero(const struct MDeformVert *dvert, const int defgr return true; } -/** - * \return The total weight in all groups marked in the selection mask. - */ float BKE_defvert_total_selected_weight(const struct MDeformVert *dv, int defbase_tot, const bool *defbase_sel) @@ -961,14 +907,6 @@ float BKE_defvert_total_selected_weight(const struct MDeformVert *dv, return total; } -/** - * \return The representative weight of a multipaint group, used for - * viewport colors and actual painting. - * - * Result equal to sum of weights with auto normalize, and average otherwise. - * Value is not clamped, since painting relies on multiplication being always - * commutative with the collective weight function. - */ float BKE_defvert_multipaint_collective_weight(const struct MDeformVert *dv, int defbase_tot, const bool *defbase_sel, @@ -986,11 +924,6 @@ float BKE_defvert_multipaint_collective_weight(const struct MDeformVert *dv, return total; } -/** - * Computes the display weight for the lock relative weight paint mode. - * - * \return weight divided by 1-locked_weight with division by zero check - */ float BKE_defvert_calc_lock_relative_weight(float weight, float locked_weight, float unlocked_weight) @@ -1019,11 +952,6 @@ float BKE_defvert_calc_lock_relative_weight(float weight, return weight / (1.0f - locked_weight); } -/** - * Computes the display weight for the lock relative weight paint mode, using weight data. - * - * \return weight divided by unlocked, or 1-locked_weight with division by zero check. - */ float BKE_defvert_lock_relative_weight(float weight, const struct MDeformVert *dv, int defbase_tot, @@ -1115,10 +1043,6 @@ void BKE_defvert_extract_vgroup_to_vertweights(MDeformVert *dvert, } } -/** - * The following three make basic interpolation, - * using temp vert_weights array to avoid looking up same weight several times. - */ void BKE_defvert_extract_vgroup_to_edgeweights(MDeformVert *dvert, const int defgroup, const int num_verts, @@ -1451,7 +1375,7 @@ bool data_transfer_layersmapping_vgroups(ListBase *r_map, * 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. + * NOTE: Above comment is outdated, but this function was written when that was true. */ const ListBase *src_defbase = BKE_object_defgroup_list(ob_src); diff --git a/source/blender/blenkernel/intern/displist.cc b/source/blender/blenkernel/intern/displist.cc index 0776f3b9a68..78177095a77 100644 --- a/source/blender/blenkernel/intern/displist.cc +++ b/source/blender/blenkernel/intern/displist.cc @@ -47,7 +47,6 @@ #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" @@ -58,6 +57,7 @@ #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. */ @@ -66,8 +66,6 @@ using blender::IndexRange; -static void boundbox_displist_object(Object *ob); - static void displist_elem_free(DispList *dl) { if (dl) { @@ -318,7 +316,7 @@ static void curve_to_displist(const Curve *cu, * and resolution > 1. */ const bool use_cyclic_sample = is_cyclic && (samples_len != 2); - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); /* Add one to the length because of 'BKE_curve_forward_diff_bezier'. */ dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * (samples_len + 1), __func__); BLI_addtail(r_dispbase, dl); @@ -373,7 +371,7 @@ static void curve_to_displist(const Curve *cu, } else if (nu->type == CU_NURBS) { const int len = (resolution * SEGMENTSU(nu)); - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), __func__); BLI_addtail(r_dispbase, dl); dl->parts = 1; @@ -386,7 +384,7 @@ static void curve_to_displist(const Curve *cu, } else if (nu->type == CU_POLY) { const int len = nu->pntsu; - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), __func__); BLI_addtail(r_dispbase, dl); dl->parts = 1; @@ -404,12 +402,6 @@ static void curve_to_displist(const Curve *cu, } } -/** - * \param normal_proj: Optional normal that's used to project the scanfill verts into 2d coords. - * Pass this along if known since it saves time calculating the normal. - * This is also used to initialize #DispList.nors (one normal per display list). - * \param flipnormal: Flip the normal (same as passing \a normal_proj negated) - */ void BKE_displist_fill(const ListBase *dispbase, ListBase *to, const float normal_proj[3], @@ -483,7 +475,7 @@ void BKE_displist_fill(const ListBase *dispbase, const int triangles_len = BLI_scanfill_calc_ex(&sf_ctx, scanfill_flag, normal_proj); if (totvert != 0 && triangles_len != 0) { - DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = MEM_cnew<DispList>(__func__); dlnew->type = DL_INDEX3; dlnew->flag = (dl_flag_accum & (DL_BACK_CURVE | DL_FRONT_CURVE)); dlnew->rt = (dl_rt_accum & CU_SMOOTH); @@ -538,7 +530,7 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) if (dl->type == DL_SURF) { if ((dl->flag & DL_CYCL_V) && (dl->flag & DL_CYCL_U) == 0) { if ((cu->flag & CU_BACK) && (dl->flag & DL_BACK_CURVE)) { - DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = MEM_cnew<DispList>(__func__); BLI_addtail(&front, dlnew); dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; @@ -557,7 +549,7 @@ static void bevels_to_filledpoly(const Curve *cu, ListBase *dispbase) } } if ((cu->flag & CU_FRONT) && (dl->flag & DL_FRONT_CURVE)) { - DispList *dlnew = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dlnew = MEM_cnew<DispList>(__func__); BLI_addtail(&back, dlnew); dlnew->verts = (float *)MEM_mallocN(sizeof(float[3]) * dl->parts, __func__); dlnew->nr = dl->parts; @@ -673,7 +665,7 @@ void BKE_displist_make_mball(Depsgraph *depsgraph, Scene *scene, Object *ob) BKE_displist_free(&(ob->runtime.curve_cache->disp)); } else { - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); + ob->runtime.curve_cache = MEM_cnew<CurveCache>(__func__); } BKE_mball_polygonize(depsgraph, scene, ob, &ob->runtime.curve_cache->disp); @@ -832,7 +824,7 @@ static bool do_curve_implicit_mesh_conversion(const Curve *curve, } /* Curve objects with implicit "tube" meshes should convert implicitly to a mesh. */ - if (curve->ext1 != 0.0f || curve->ext2 != 0.0f) { + if (curve->extrude != 0.0f || curve->bevel_radius != 0.0f) { return true; } @@ -912,7 +904,7 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, 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); + BKE_mesh_vertex_normals_ensure(mesh); } mti->deformVerts(md, &mectx_deform, mesh, vertex_coords, totvert); BKE_mesh_vert_coords_apply(mesh, vertex_coords); @@ -920,7 +912,7 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, } else { if (mti->dependsOnNormals != nullptr && mti->dependsOnNormals(md)) { - BKE_mesh_ensure_normals(mesh); + BKE_mesh_vertex_normals_ensure(mesh); } Mesh *output_mesh = mti->modifyMesh(md, &mectx_apply, mesh); if (mesh != output_mesh) { @@ -932,7 +924,7 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph, if (geometry_set.has_mesh()) { Mesh *final_mesh = geometry_set.get_mesh_for_write(); - BKE_mesh_calc_normals(final_mesh); + BKE_mesh_ensure_normals_for_display(final_mesh); BLI_strncpy(final_mesh->id.name, cu->id.name, sizeof(final_mesh->id.name)); *((short *)final_mesh->id.name) = ID_ME; @@ -1004,7 +996,7 @@ static void evaluate_surface_object(Depsgraph *depsgraph, if (nu->pntsv == 1) { const int len = SEGMENTSU(nu) * resolu; - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), __func__); BLI_addtail(r_dispbase, dl); @@ -1027,7 +1019,7 @@ static void evaluate_surface_object(Depsgraph *depsgraph, else { const int len = (nu->pntsu * resolu) * (nu->pntsv * resolv); - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = (float *)MEM_mallocN(len * sizeof(float[3]), __func__); BLI_addtail(r_dispbase, dl); @@ -1130,7 +1122,7 @@ static void fillBevelCap(const Nurb *nu, const float *prev_fp, ListBase *dispbase) { - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr, __func__); memcpy(dl->verts, prev_fp, sizeof(float[3]) * dlb->nr); @@ -1312,11 +1304,11 @@ static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, ListBase dlbev = BKE_curve_bevel_make(cu); /* no bevel or extrude, and no width correction? */ - if (BLI_listbase_is_empty(&dlbev) && cu->width == 1.0f) { + if (BLI_listbase_is_empty(&dlbev) && cu->offset == 1.0f) { curve_to_displist(cu, deformed_nurbs, for_render, r_dispbase); } else { - const float widfac = cu->width - 1.0f; + const float widfac = cu->offset - 1.0f; const BevList *bl = (BevList *)ob->runtime.curve_cache->bev.first; const Nurb *nu = (Nurb *)deformed_nurbs->first; @@ -1329,7 +1321,7 @@ static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, /* exception handling; curve without bevel or extrude, with width correction */ if (BLI_listbase_is_empty(&dlbev)) { - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), "makeDispListbev"); + DispList *dl = MEM_cnew<DispList>("makeDispListbev"); dl->verts = (float *)MEM_mallocN(sizeof(float[3]) * bl->nr, "dlverts"); BLI_addtail(r_dispbase, dl); @@ -1379,7 +1371,7 @@ static GeometrySet evaluate_curve_type_object(Depsgraph *depsgraph, LISTBASE_FOREACH (DispList *, dlb, &dlbev) { /* for each part of the bevel use a separate displblock */ - DispList *dl = (DispList *)MEM_callocN(sizeof(DispList), __func__); + DispList *dl = MEM_cnew<DispList>(__func__); dl->verts = data = (float *)MEM_mallocN(sizeof(float[3]) * dlb->nr * steps, __func__); BLI_addtail(r_dispbase, dl); @@ -1503,7 +1495,7 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, BKE_object_free_derived_caches(ob); cow_curve.curve_eval = nullptr; - ob->runtime.curve_cache = (CurveCache *)MEM_callocN(sizeof(CurveCache), __func__); + ob->runtime.curve_cache = MEM_cnew<CurveCache>(__func__); ListBase *dispbase = &ob->runtime.curve_cache->disp; if (ob->type == OB_SURF) { @@ -1524,20 +1516,11 @@ void BKE_displist_make_curveTypes(Depsgraph *depsgraph, cow_curve.curve_eval = curve_component.get_for_write(); BKE_object_eval_assign_data(ob, &cow_curve.id, false); } - else if (geometry.has_mesh()) { - /* Most areas of Blender don't yet know how to look in #geometry_set_eval for evaluated mesh - * data, and look in #data_eval instead. When the object evaluates to a curve, that field - * must be used for the evaluated curve data, but otherwise we can use the field to store a - * pointer to the mesh, so more areas can retrieve the mesh. */ - MeshComponent &mesh_component = geometry.get_component_for_write<MeshComponent>(); - Mesh *mesh_eval = mesh_component.get_for_write(); - BKE_object_eval_assign_data(ob, &mesh_eval->id, false); - } ob->runtime.geometry_set_eval = new GeometrySet(std::move(geometry)); } - boundbox_displist_object(ob); + BKE_object_boundbox_calc_from_evaluated_geometry(ob); } void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) @@ -1545,7 +1528,7 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) bool doit = false; LISTBASE_FOREACH (const DispList *, dl, dispbase) { - const int tot = (dl->type == DL_INDEX3) ? dl->nr : dl->nr * dl->parts; + const int tot = (ELEM(dl->type, DL_INDEX3, DL_INDEX4)) ? dl->nr : dl->nr * dl->parts; for (const int i : IndexRange(tot)) { minmax_v3v3_v3(min, max, &dl->verts[i * 3]); } @@ -1560,30 +1543,3 @@ void BKE_displist_minmax(const ListBase *dispbase, float min[3], float max[3]) zero_v3(max); } } - -/* this is confusing, there's also min_max_object, applying the obmat... */ -static void boundbox_displist_object(Object *ob) -{ - 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__); - } - - 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); - - 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 5c969d52aea..4451961ad94 100644 --- a/source/blender/blenkernel/intern/displist_tangent.c +++ b/source/blender/blenkernel/intern/displist_tangent.c @@ -29,6 +29,10 @@ /* interface */ #include "mikktspace.h" +/* -------------------------------------------------------------------- */ +/** \name Internal Types + * \{ */ + typedef struct { const DispList *dl; float (*tangent)[4]; /* destination */ diff --git a/source/blender/blenkernel/intern/dynamicpaint.c b/source/blender/blenkernel/intern/dynamicpaint.c index 9083c507160..64e0427a810 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 */ @@ -327,7 +327,6 @@ static int dynamicPaint_surfaceNumOfPoints(DynamicPaintSurface *surface) return 0; } -/* get currently active surface (in user interface) */ DynamicPaintSurface *get_activeSurface(DynamicPaintCanvasSettings *canvas) { return BLI_findlink(&canvas->surfaces, canvas->active_sur); @@ -420,7 +419,6 @@ void dynamicPaintSurface_setUniqueName(DynamicPaintSurface *surface, const char surface_duplicateNameExists, surface, name, '.', surface->name, sizeof(surface->name)); } -/* change surface data to defaults on new type */ void dynamicPaintSurface_updateType(struct DynamicPaintSurface *surface) { if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) { @@ -763,7 +761,7 @@ static void surfaceGenerateGrid(struct DynamicPaintSurface *surface) copy_v3_v3(bData->dim, dim); min_dim = max_fff(td[0], td[1], td[2]) / 1000.0f; - /* deactivate zero axises */ + /* deactivate zero axes */ for (i = 0; i < 3; i++) { if (td[i] < min_dim) { td[i] = 1.0f; @@ -784,7 +782,7 @@ static void surfaceGenerateGrid(struct DynamicPaintSurface *surface) dim_factor = (float)pow((double)volume / ((double)sData->total_points / 10000.0), 1.0 / (double)axis); - /* define final grid size using dim_factor, use min 3 for active axises */ + /* define final grid size using dim_factor, use min 3 for active axes */ for (i = 0; i < 3; i++) { grid->dim[i] = (int)floor(td[i] / dim_factor); CLAMP(grid->dim[i], (dim[i] >= min_dim) ? 3 : 1, 100); @@ -855,7 +853,6 @@ static void surfaceGenerateGrid(struct DynamicPaintSurface *surface) /***************************** Freeing data ******************************/ -/* Free brush data */ void dynamicPaint_freeBrush(struct DynamicPaintModifierData *pmd) { if (pmd->brush) { @@ -992,7 +989,6 @@ void dynamicPaint_freeSurface(const DynamicPaintModifierData *pmd, DynamicPaintS MEM_freeN(surface); } -/* Free canvas data */ void dynamicPaint_freeCanvas(DynamicPaintModifierData *pmd) { if (pmd->canvas) { @@ -1011,7 +1007,6 @@ void dynamicPaint_freeCanvas(DynamicPaintModifierData *pmd) } } -/* Free whole dp modifier */ void dynamicPaint_Modifier_free(DynamicPaintModifierData *pmd) { if (pmd == NULL) { @@ -1024,11 +1019,6 @@ void dynamicPaint_Modifier_free(DynamicPaintModifierData *pmd) /***************************** Initialize and reset ******************************/ -/* - * Creates a new surface and adds it to the list - * If scene is null, frame range of 1-250 is used - * A pointer to this surface is returned - */ DynamicPaintSurface *dynamicPaint_createNewSurface(DynamicPaintCanvasSettings *canvas, Scene *scene) { @@ -1106,9 +1096,6 @@ DynamicPaintSurface *dynamicPaint_createNewSurface(DynamicPaintCanvasSettings *c return surface; } -/* - * Initialize modifier data - */ bool dynamicPaint_createType(struct DynamicPaintModifierData *pmd, int type, struct Scene *scene) { if (pmd) { @@ -1721,7 +1708,6 @@ static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface } } -/* clears surface data back to zero */ void dynamicPaint_clearSurface(const Scene *scene, DynamicPaintSurface *surface) { PaintSurfaceData *sData = surface->data; @@ -1751,7 +1737,6 @@ void dynamicPaint_clearSurface(const Scene *scene, DynamicPaintSurface *surface) } } -/* Completely (re)initializes surface (only for point cache types). */ bool dynamicPaint_resetSurface(const Scene *scene, DynamicPaintSurface *surface) { int numOfPoints = dynamicPaint_surfaceNumOfPoints(surface); @@ -1804,6 +1789,7 @@ typedef struct DynamicPaintModifierApplyData { Object *ob; MVert *mvert; + const float (*vert_normals)[3]; const MLoop *mloop; const MPoly *mpoly; @@ -1821,14 +1807,11 @@ static void dynamic_paint_apply_surface_displace_cb(void *__restrict userdata, const DynamicPaintSurface *surface = data->surface; MVert *mvert = data->mvert; - float normal[3]; const float *value = (float *)surface->data->type_data; const float val = value[i] * surface->disp_factor; - normal_short_to_float_v3(normal, mvert[i].no); - /* same as 'mvert[i].co[0] -= normal[0] * val' etc. */ - madd_v3_v3fl(mvert[i].co, normal, -val); + madd_v3_v3fl(mvert[i].co, data->vert_normals[i], -val); } /* apply displacing vertex surface to the derived mesh */ @@ -1847,6 +1830,7 @@ static void dynamicPaint_applySurfaceDisplace(DynamicPaintSurface *surface, Mesh DynamicPaintModifierApplyData data = { .surface = surface, .mvert = mvert, + .vert_normals = BKE_mesh_vertex_normals_ensure(result), }; TaskParallelSettings settings; BLI_parallel_range_settings_defaults(&settings); @@ -1913,10 +1897,8 @@ static void dynamic_paint_apply_surface_wave_cb(void *__restrict userdata, PaintWavePoint *wPoint = (PaintWavePoint *)data->surface->data->type_data; MVert *mvert = data->mvert; - float normal[3]; - normal_short_to_float_v3(normal, mvert[i].no); - madd_v3_v3fl(mvert[i].co, normal, wPoint[i].height); + madd_v3_v3fl(mvert[i].co, data->vert_normals[i], wPoint[i].height); } /* @@ -2045,6 +2027,7 @@ static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object * DynamicPaintModifierApplyData data = { .surface = surface, .mvert = mvert, + .vert_normals = BKE_mesh_vertex_normals_ensure(result), }; TaskParallelSettings settings; BLI_parallel_range_settings_defaults(&settings); @@ -2079,7 +2062,6 @@ static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object * return result; } -/* update cache frame range */ void dynamicPaint_cacheUpdateFrames(DynamicPaintSurface *surface) { if (surface->pointcache) { @@ -2189,7 +2171,6 @@ static void dynamicPaint_frameUpdate(DynamicPaintModifierData *pmd, } } -/* Modifier call. Processes dynamic paint modifier step. */ Mesh *dynamicPaint_Modifier_do(DynamicPaintModifierData *pmd, struct Depsgraph *depsgraph, Scene *scene, @@ -3436,7 +3417,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. * @@ -4107,7 +4088,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) { @@ -4304,6 +4285,7 @@ static bool dynamicPaint_paintMesh(Depsgraph *depsgraph, mesh = BKE_mesh_copy_for_eval(brush_mesh, false); mvert = mesh->mvert; + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); mlooptri = BKE_mesh_runtime_looptri_ensure(mesh); mloop = mesh->mloop; numOfVerts = mesh->totvert; @@ -4318,7 +4300,7 @@ static bool dynamicPaint_paintMesh(Depsgraph *depsgraph, /* for proximity project calculate average normal */ if (brush->flags & MOD_DPAINT_PROX_PROJECT && brush->collision != MOD_DPAINT_COL_VOLUME) { float nor[3]; - normal_short_to_float_v3(nor, mvert[ii].no); + copy_v3_v3(nor, vert_normals[ii]); mul_mat3_m4_v3(brushOb->obmat, nor); normalize_v3(nor); @@ -4597,7 +4579,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); @@ -5883,8 +5865,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( @@ -5927,6 +5908,7 @@ typedef struct DynamicPaintGenerateBakeData { Object *ob; const MVert *mvert; + const float (*vert_normals)[3]; const Vec3f *canvas_verts; const bool do_velocity_data; @@ -5946,7 +5928,6 @@ static void dynamic_paint_generate_bake_data_cb(void *__restrict userdata, Object *ob = data->ob; - const MVert *mvert = data->mvert; const Vec3f *canvas_verts = data->canvas_verts; const bool do_velocity_data = data->do_velocity_data; @@ -5980,9 +5961,9 @@ static void dynamic_paint_generate_bake_data_cb(void *__restrict userdata, } /* Calculate current pixel surface normal */ - normal_short_to_float_v3(n1, mvert[tPoint->v1].no); - normal_short_to_float_v3(n2, mvert[tPoint->v2].no); - normal_short_to_float_v3(n3, mvert[tPoint->v3].no); + copy_v3_v3(n1, data->vert_normals[tPoint->v1]); + copy_v3_v3(n2, data->vert_normals[tPoint->v2]); + copy_v3_v3(n3, data->vert_normals[tPoint->v3]); interp_v3_v3v3v3( temp_nor, n1, n2, n3, f_data->barycentricWeights[index * bData->s_num[index]].v); @@ -6024,7 +6005,7 @@ static void dynamic_paint_generate_bake_data_cb(void *__restrict userdata, } /* normal */ - normal_short_to_float_v3(temp_nor, mvert[index].no); + copy_v3_v3(temp_nor, data->vert_normals[index]); if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) { /* Prepare surface normal directional scale to easily convert * brush intersection amount between global and local space */ @@ -6163,6 +6144,7 @@ static bool dynamicPaint_generateBakeData(DynamicPaintSurface *surface, .surface = surface, .ob = ob, .mvert = mvert, + .vert_normals = BKE_mesh_vertex_normals_ensure(mesh), .canvas_verts = canvas_verts, .do_velocity_data = do_velocity_data, .new_bdata = new_bdata, @@ -6370,9 +6352,6 @@ static int dynamicPaint_doStep(Depsgraph *depsgraph, return ret; } -/** - * Calculate a single frame and included sub-frames for surface. - */ int dynamicPaint_calculateFrame(DynamicPaintSurface *surface, struct Depsgraph *depsgraph, Scene *scene, diff --git a/source/blender/blenkernel/intern/editmesh.c b/source/blender/blenkernel/intern/editmesh.c index 83e03ef44f5..0774a1a3d88 100644 --- a/source/blender/blenkernel/intern/editmesh.c +++ b/source/blender/blenkernel/intern/editmesh.c @@ -39,10 +39,8 @@ #include "BKE_mesh_wrapper.h" #include "BKE_object.h" -/** - * \note The caller is responsible for ensuring triangulation data, - * typically by calling #BKE_editmesh_looptri_calc. - */ +#include "DEG_depsgraph_query.h" + BMEditMesh *BKE_editmesh_create(BMesh *bm) { BMEditMesh *em = MEM_callocN(sizeof(BMEditMesh), __func__); @@ -55,9 +53,6 @@ BMEditMesh *BKE_editmesh_copy(BMEditMesh *em) BMEditMesh *em_copy = MEM_callocN(sizeof(BMEditMesh), __func__); *em_copy = *em; - em_copy->mesh_eval_cage = em_copy->mesh_eval_final = NULL; - em_copy->bb_cage = NULL; - em_copy->bm = BM_mesh_copy(em->bm); /* The tessellation is NOT calculated on the copy here, @@ -75,12 +70,6 @@ BMEditMesh *BKE_editmesh_copy(BMEditMesh *em) return em_copy; } -/** - * \brief Return the BMEditMesh for a given object - * - * \note this function assumes this is a mesh object, - * don't add NULL data check here. caller must do that - */ BMEditMesh *BKE_editmesh_from_object(Object *ob) { BLI_assert(ob->type == OB_MESH); @@ -158,10 +147,6 @@ void BKE_editmesh_looptri_calc(BMEditMesh *em) }); } -/** - * Performing the face normal calculation at the same time as tessellation - * gives a reasonable performance boost (approx ~20% faster). - */ void BKE_editmesh_looptri_and_normals_calc(BMEditMesh *em) { BKE_editmesh_looptri_calc_ex(em, @@ -208,23 +193,8 @@ void BKE_editmesh_looptri_and_normals_calc_with_partial(BMEditMesh *em, }); } -void BKE_editmesh_free_derived_caches(BMEditMesh *em) -{ - if (em->mesh_eval_cage) { - BKE_id_free(NULL, em->mesh_eval_cage); - } - if (em->mesh_eval_final && em->mesh_eval_final != em->mesh_eval_cage) { - BKE_id_free(NULL, em->mesh_eval_final); - } - em->mesh_eval_cage = em->mesh_eval_final = NULL; - - MEM_SAFE_FREE(em->bb_cage); -} - -/* Does not free the #BMEditMesh struct itself. */ void BKE_editmesh_free_data(BMEditMesh *em) { - BKE_editmesh_free_derived_caches(em); if (em->looptris) { MEM_freeN(em->looptris); @@ -244,8 +214,7 @@ struct CageUserData { static void cage_mapped_verts_callback(void *userData, int index, const float co[3], - const float UNUSED(no_f[3]), - const short UNUSED(no_s[3])) + const float UNUSED(no[3])) { struct CageUserData *data = userData; @@ -299,13 +268,15 @@ const float (*BKE_editmesh_vert_coords_when_deformed(struct Depsgraph *depsgraph *r_is_alloc = false; Mesh *me = ob->data; + Object *object_eval = DEG_get_evaluated_object(depsgraph, ob); + Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(object_eval); if ((me->runtime.edit_data != NULL) && (me->runtime.edit_data->vertexCos != NULL)) { /* Deformed, and we have deformed coords already. */ coords = me->runtime.edit_data->vertexCos; } - else if ((em->mesh_eval_final != NULL) && - (em->mesh_eval_final->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH)) { + else if ((editmesh_eval_final != NULL) && + (editmesh_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. */ } else { @@ -342,7 +313,6 @@ void BKE_editmesh_lnorspace_update(BMEditMesh *em, Mesh *me) BM_lnorspace_update(bm); } -/* If autosmooth not already set, set it */ void BKE_editmesh_ensure_autosmooth(BMEditMesh *em, Mesh *me) { if (!(me->flag & ME_AUTOSMOOTH)) { @@ -351,18 +321,18 @@ void BKE_editmesh_ensure_autosmooth(BMEditMesh *em, Mesh *me) } } -BoundBox *BKE_editmesh_cage_boundbox_get(BMEditMesh *em) +BoundBox *BKE_editmesh_cage_boundbox_get(struct Object *object, BMEditMesh *UNUSED(em)) { - if (em->bb_cage == NULL) { + if (object->runtime.editmesh_bb_cage == NULL) { float min[3], max[3]; INIT_MINMAX(min, max); - if (em->mesh_eval_cage) { - BKE_mesh_wrapper_minmax(em->mesh_eval_cage, min, max); + if (object->runtime.editmesh_eval_cage) { + BKE_mesh_wrapper_minmax(object->runtime.editmesh_eval_cage, min, max); } - em->bb_cage = MEM_callocN(sizeof(BoundBox), "BMEditMesh.bb_cage"); - BKE_boundbox_init_from_minmax(em->bb_cage, min, max); + object->runtime.editmesh_bb_cage = MEM_callocN(sizeof(BoundBox), "BMEditMesh.bb_cage"); + BKE_boundbox_init_from_minmax(object->runtime.editmesh_bb_cage, min, max); } - return em->bb_cage; + return object->runtime.editmesh_bb_cage; } diff --git a/source/blender/blenkernel/intern/editmesh_bvh.c b/source/blender/blenkernel/intern/editmesh_bvh.c index 087481b1b5d..e200a504b5d 100644 --- a/source/blender/blenkernel/intern/editmesh_bvh.c +++ b/source/blender/blenkernel/intern/editmesh_bvh.c @@ -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 */ @@ -565,9 +565,6 @@ static bool bmbvh_overlap_cb(void *userdata, int index_a, int index_b, int UNUSE ((verts_shared == 0) || (len_squared_v3v3(ix_pair[0], ix_pair[1]) > data->epsilon))); } -/** - * Overlap indices reference the looptri's - */ BVHTreeOverlap *BKE_bmbvh_overlap(const BMBVHTree *bmtree_a, const BMBVHTree *bmtree_b, unsigned int *r_overlap_tot) @@ -591,9 +588,6 @@ static bool bmbvh_overlap_self_cb(void *userdata, int index_a, int index_b, int return false; } -/** - * Overlap indices reference the looptri's - */ BVHTreeOverlap *BKE_bmbvh_overlap_self(const BMBVHTree *bmtree, unsigned int *r_overlap_tot) { struct BMBVHTree_OverlapData data; diff --git a/source/blender/blenkernel/intern/editmesh_tangent.c b/source/blender/blenkernel/intern/editmesh_tangent.c index da4ea742656..ff0d47e534e 100644 --- a/source/blender/blenkernel/intern/editmesh_tangent.c +++ b/source/blender/blenkernel/intern/editmesh_tangent.c @@ -273,13 +273,6 @@ static void emDM_calc_loop_tangents_thread(TaskPool *__restrict UNUSED(pool), vo } } -/** - * \see #BKE_mesh_calc_loop_tangent, same logic but used arrays instead of #BMesh data. - * - * \note This function is not so normal, its using #BMesh.ldata as input, - * but output's to #Mesh.ldata. - * This is done because #CD_TANGENT is cache data used only for drawing. - */ void BKE_editmesh_loop_tangent_calc(BMEditMesh *em, bool calc_active_tangent, const char (*tangent_names)[MAX_NAME], diff --git a/source/blender/blenkernel/intern/effect.c b/source/blender/blenkernel/intern/effect.c index a88339082fe..bbf9e9edfd2 100644 --- a/source/blender/blenkernel/intern/effect.c +++ b/source/blender/blenkernel/intern/effect.c @@ -59,6 +59,7 @@ #include "BKE_fluid.h" #include "BKE_global.h" #include "BKE_layer.h" +#include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_object.h" #include "BKE_particle.h" @@ -221,9 +222,6 @@ static void add_effector_evaluation(ListBase **effectors, precalculate_effector(depsgraph, eff); } -/* Create list of effector relations in the collection or entire scene. - * This is used by the depsgraph to build relations, as well as faster - * lookup of effectors during evaluation. */ ListBase *BKE_effector_relations_create(Depsgraph *depsgraph, ViewLayer *view_layer, Collection *collection) @@ -329,7 +327,6 @@ static bool is_effector_relevant(PartDeflect *pd, EffectorWeights *weights, bool is_effector_nonzero_strength(pd); } -/* Create effective list of effectors from relations built beforehand. */ ListBase *BKE_effectors_create(Depsgraph *depsgraph, Object *ob_src, ParticleSystem *psys_src, @@ -656,11 +653,11 @@ float effector_falloff(EffectorCache *eff, return falloff; } -int closest_point_on_surface(SurfaceModifierData *surmd, - const float co[3], - float surface_co[3], - float surface_nor[3], - float surface_vel[3]) +bool closest_point_on_surface(SurfaceModifierData *surmd, + const float co[3], + float surface_co[3], + float surface_nor[3], + float surface_vel[3]) { BVHTreeNearest nearest; @@ -687,18 +684,18 @@ int closest_point_on_surface(SurfaceModifierData *surmd, mul_v3_fl(surface_vel, (1.0f / 3.0f)); } - return 1; + return true; } - return 0; + return false; } -int get_effector_data(EffectorCache *eff, - EffectorData *efd, - EffectedPoint *point, - int real_velocity) +bool get_effector_data(EffectorCache *eff, + EffectorData *efd, + EffectedPoint *point, + int real_velocity) { float cfra = DEG_get_ctime(eff->depsgraph); - int ret = 0; + bool ret = false; /* In case surface object is in Edit mode when loading the .blend, * surface modifier is never executed and bvhtree never built, see T48415. */ @@ -719,9 +716,10 @@ int get_effector_data(EffectorCache *eff, else if (eff->pd && eff->pd->shape == PFIELD_SHAPE_POINTS) { /* TODO: hair and points object support */ const Mesh *me_eval = BKE_object_get_evaluated_mesh(eff->ob); + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me_eval); 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); + copy_v3_v3(efd->nor, vert_normals[*efd->index]); mul_m4_v3(eff->ob->obmat, efd->loc); mul_mat3_m4_v3(eff->ob->obmat, efd->nor); @@ -730,7 +728,7 @@ int get_effector_data(EffectorCache *eff, efd->size = 0.0f; - ret = 1; + ret = true; } } else if (eff->psys) { @@ -798,7 +796,7 @@ int get_effector_data(EffectorCache *eff, zero_v3(efd->vel); efd->size = 0.0f; - ret = 1; + ret = true; } if (ret) { @@ -1130,20 +1128,6 @@ static void do_physical_effector(EffectorCache *eff, } } -/* -------- BKE_effectors_apply() -------- - * generic force/speed system, now used for particles and softbodies - * scene = scene where it runs in, for time and stuff - * lb = listbase with objects that take part in effecting - * opco = global coord, as input - * force = accumulator for force - * wind_force = accumulator for force only acting perpendicular to a surface - * speed = actual current speed which can be altered - * cur_time = "external" time in frames, is constant for static particles - * loc_time = "local" time in frames, range <0-1> for the lifetime of particle - * par_layer = layer the caller is in - * flags = only used for softbody wind now - * guide = old speed of particle - */ void BKE_effectors_apply(ListBase *effectors, ListBase *colliders, EffectorWeights *weights, @@ -1152,6 +1136,22 @@ void BKE_effectors_apply(ListBase *effectors, float *wind_force, float *impulse) { + /* WARNING(@campbellbarton): historic comment? + * Many of these parameters don't exist! + * + * scene = scene where it runs in, for time and stuff. + * lb = listbase with objects that take part in effecting. + * opco = global coord, as input. + * force = accumulator for force. + * wind_force = accumulator for force only acting perpendicular to a surface. + * speed = actual current speed which can be altered. + * cur_time = "external" time in frames, is constant for static particles. + * loc_time = "local" time in frames, range <0-1> for the lifetime of particle. + * par_layer = layer the caller is in. + * flags = only used for soft-body wind now. + * guide = old speed of particle. + */ + /* * Modifies the force on a particle according to its * relation with the effector object diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c index 8e9c504dcbf..f7a547543af 100644 --- a/source/blender/blenkernel/intern/fcurve.c +++ b/source/blender/blenkernel/intern/fcurve.c @@ -77,7 +77,6 @@ FCurve *BKE_fcurve_create(void) /** \name F-Curve Data Free * \{ */ -/* Frees the F-Curve itself too, so make sure BLI_remlink is called before calling this... */ void BKE_fcurve_free(FCurve *fcu) { if (fcu == NULL) { @@ -99,7 +98,6 @@ void BKE_fcurve_free(FCurve *fcu) MEM_freeN(fcu); } -/* Frees a list of F-Curves. */ void BKE_fcurves_free(ListBase *list) { FCurve *fcu, *fcn; @@ -128,7 +126,6 @@ void BKE_fcurves_free(ListBase *list) /** \name F-Curve Data Copy * \{ */ -/* Duplicate a F-Curve. */ FCurve *BKE_fcurve_copy(const FCurve *fcu) { FCurve *fcu_d; @@ -161,7 +158,6 @@ FCurve *BKE_fcurve_copy(const FCurve *fcu) return fcu_d; } -/* Duplicate a list of F-Curves. */ void BKE_fcurves_copy(ListBase *dst, ListBase *src) { FCurve *dfcu, *sfcu; @@ -181,10 +177,6 @@ void BKE_fcurves_copy(ListBase *dst, ListBase *src) } } -/** - * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). - */ void BKE_fcurve_foreach_id(FCurve *fcu, LibraryForeachIDData *data) { ChannelDriver *driver = fcu->driver; @@ -203,21 +195,24 @@ 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; } } } /* ----------------- Finding F-Curves -------------------------- */ -/* High level function to get an fcurve from C without having the RNA. */ FCurve *id_data_find_fcurve( ID *id, void *data, StructRNA *type, const char *prop_name, int index, bool *r_driven) { @@ -269,8 +264,6 @@ FCurve *id_data_find_fcurve( return fcu; } -/* Find the F-Curve affecting the given RNA-access path + index, - * in the list of F-Curves provided. */ FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_index) { FCurve *fcu; @@ -300,7 +293,6 @@ FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_i /** \name FCurve Iteration * \{ */ -/* Quick way to loop over all fcurves of a given 'path'. */ FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[]) { FCurve *fcu; @@ -321,18 +313,6 @@ FCurve *BKE_fcurve_iter_step(FCurve *fcu_iter, const char rna_path[]) return NULL; } -/** - * Get list of LinkData's containing pointers to the F-Curves - * which control the types of data indicated. - * - * Lists... - * - dst: list of LinkData's matching the criteria returned. - * List must be freed after use, and is assumed to be empty when passed. - * - src: list of F-Curves to search through - * Filters... - * - dataPrefix: i.e. 'pose.bones[' or 'nodes[' - * - dataName: name of entity within "" immediately following the prefix - */ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, const char *dataName) { FCurve *fcu; @@ -432,7 +412,7 @@ FCurve *BKE_fcurve_find_by_rna_context_ui(bContext *C, char *path = NULL; if (!adt && C) { - path = BKE_animdata_driver_path_hack(C, &tptr, prop, NULL); + path = RNA_path_from_ID_to_property(&tptr, prop); adt = BKE_animdata_from_id(tptr.owner_id); step--; } @@ -483,7 +463,7 @@ FCurve *BKE_fcurve_find_by_rna_context_ui(bContext *C, } if (step) { - char *tpath = BKE_animdata_driver_path_hack(C, &tptr, prop, path); + char *tpath = path ? path : RNA_path_from_ID_to_property(&tptr, prop); if (tpath && tpath != path) { MEM_freeN(path); path = tpath; @@ -597,9 +577,6 @@ static int BKE_fcurve_bezt_binarysearch_index_ex(const BezTriple array[], return start; } -/* 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(const BezTriple array[], const float frame, const int arraylen, @@ -663,7 +640,6 @@ static short get_fcurve_end_keyframes(FCurve *fcu, return found; } -/* Calculate the extents of F-Curve's data. */ bool BKE_fcurve_calc_bounds(FCurve *fcu, float *xmin, float *xmax, @@ -794,7 +770,6 @@ bool BKE_fcurve_calc_bounds(FCurve *fcu, return foundvert; } -/* Calculate the extents of F-Curve's keyframes. */ bool BKE_fcurve_calc_range( FCurve *fcu, float *start, float *end, const bool do_sel_only, const bool do_min_length) { @@ -842,14 +817,6 @@ bool BKE_fcurve_calc_range( return foundvert; } -/** - * Return an array of keyed frames, rounded to `interval`. - * - * \param interval: Set to 1.0 to round to whole keyframes, 0.5 for in-between key-frames, etc. - * - * \note An interval of zero could be supported (this implies no rounding at all), - * however this risks very small differences in float values being treated as separate keyframes. - */ float *BKE_fcurves_calc_keyed_frames_ex(FCurve **fcurve_array, int fcurve_array_len, const float interval, @@ -898,10 +865,6 @@ float *BKE_fcurves_calc_keyed_frames(FCurve **fcurve_array, /** \name Active Keyframe * \{ */ -/** - * Set the index that stores the FCurve's active keyframe, assuming that \a active_bezt - * is already part of `fcu->bezt`. If NULL, set active keyframe index to "none." - */ void BKE_fcurve_active_keyframe_set(FCurve *fcu, const BezTriple *active_bezt) { if (active_bezt == NULL) { @@ -923,9 +886,6 @@ void BKE_fcurve_active_keyframe_set(FCurve *fcu, const BezTriple *active_bezt) fcu->active_keyframe_index = (int)offset; } -/** - * Get the active keyframe index, with sanity checks for point bounds. - */ int BKE_fcurve_active_keyframe_index(const FCurve *fcu) { const int active_keyframe_index = fcu->active_keyframe_index; @@ -959,10 +919,6 @@ void BKE_fcurve_keyframe_move_value_with_handles(struct BezTriple *keyframe, con /** \name Status Checks * \{ */ -/* Are keyframes on F-Curve of any use? - * Usability of keyframes refers to whether they should be displayed, - * and also whether they will have any influence on the final result. - */ bool BKE_fcurve_are_keyframes_usable(FCurve *fcu) { /* F-Curve must exist. */ @@ -1028,9 +984,6 @@ bool BKE_fcurve_is_protected(FCurve *fcu) return ((fcu->flag & FCURVE_PROTECTED) || ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED))); } -/* Can keyframes be added to F-Curve? - * Keyframes can only be added if they are already visible. - */ bool BKE_fcurve_is_keyframable(FCurve *fcu) { /* F-Curve's keyframes must be "usable" (i.e. visible + have an effect on final result) */ @@ -1053,7 +1006,6 @@ bool BKE_fcurve_is_keyframable(FCurve *fcu) /** \name Keyframe Column Tools * \{ */ -/* Add a BezTriple to a column. */ static void UNUSED_FUNCTION(bezt_add_to_cfra_elem)(ListBase *lb, BezTriple *bezt) { CfraElem *ce, *cen; @@ -1096,18 +1048,12 @@ static void UNUSED_FUNCTION(bezt_add_to_cfra_elem)(ListBase *lb, BezTriple *bezt * which BezTriples/Keyframe data are ill equipped to do. */ -/* Basic sampling callback which acts as a wrapper for evaluate_fcurve() - * 'data' arg here is unneeded here... - */ float fcurve_samplingcb_evalcurve(FCurve *fcu, void *UNUSED(data), float evaltime) { /* Assume any interference from drivers on the curve is intended... */ return evaluate_fcurve(fcu, evaltime); } -/* Main API function for creating a set of sampled curve data, given some callback function - * used to retrieve the values to store. - */ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSampleFunc sample_cb) { FPoint *fpt, *new_fpt; @@ -1155,7 +1101,6 @@ static void init_unbaked_bezt_data(BezTriple *bezt) bezt->h1 = bezt->h2 = HD_AUTO_ANIM; } -/* Convert baked/sampled fcurves into bezt/regular fcurves. */ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end) { @@ -1231,7 +1176,6 @@ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end) * that the handles are correct. */ -/* Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. */ eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu) { FModifier *fcm = fcu->modifiers.first; @@ -1265,8 +1209,6 @@ eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu) return FCU_CYCLE_NONE; } -/* Checks if the F-Curve has a Cycles modifier with simple settings - * that warrant transition smoothing. */ bool BKE_fcurve_is_cyclic(FCurve *fcu) { return BKE_fcurve_get_cycle_type(fcu) != FCU_CYCLE_NONE; @@ -1295,13 +1237,6 @@ static BezTriple *cycle_offset_triple( return out; } -/** - * Variant of #calchandles_fcurve() that allows calculating based on a different select flag. - * - * \param handle_sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. - * Usually `SELECT`, but may want to use a different one at times - * (if caller does not operate on selection). - */ void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) { BezTriple *bezt, *prev, *next; @@ -1385,28 +1320,11 @@ void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag) } } -/** - * This function recalculates the handles of an F-Curve. Acts based on selection with `SELECT` - * flag. To use a different flag, use #calchandles_fcurve_ex(). - * - * If the BezTriples have been rearranged, sort them first before using this. - */ void calchandles_fcurve(FCurve *fcu) { calchandles_fcurve_ex(fcu, SELECT); } -/** - * Update handles, making sure the handle-types are valid (e.g. correctly deduced from an "Auto" - * type), and recalculating their position vectors. - * Use when something has changed handle positions. - * - * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`, - * but may want to use a different one at times (if caller does not operate on - * selection). - * \param use_handle: Check selection state of individual handles, otherwise always update both - * handles if the key is selected. - */ void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle) { BezTriple *bezt; @@ -1426,9 +1344,6 @@ void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_ha calchandles_fcurve_ex(fcu, sel_flag); } -/* This function sorts BezTriples so that they are arranged in chronological order, - * as tools working on F-Curves expect that the BezTriples are in order. - */ void sort_time_fcurve(FCurve *fcu) { if (fcu->bezt == NULL) { @@ -1471,7 +1386,6 @@ void sort_time_fcurve(FCurve *fcu) } } -/* This function tests if any BezTriples are out of order, thus requiring a sort. */ bool test_time_fcurve(FCurve *fcu) { unsigned int a; @@ -1513,14 +1427,6 @@ bool test_time_fcurve(FCurve *fcu) /** \name F-Curve Calculations * \{ */ -/** - * The length of each handle is not allowed to be more - * than the horizontal distance between (v1-v4). - * This is to prevent curve loops. - * - * This function is very similar to BKE_curve_correct_bezpart(), but allows a steeper tangent for - * more snappy animations. This is not desired for other areas in which curves are used, though. - */ void BKE_fcurve_correct_bezpart(const float v1[2], float v2[2], float v3[2], const float v4[2]) { float h1[2], h2[2], len1, len2, len, fac; @@ -1705,14 +1611,6 @@ static void berekeny(float f1, float f2, float f3, float f4, float *o, int b) } } -/** - * Adjust Bezier handles of all three given BezTriples, so that `bezt` can be inserted between - * `prev` and `next` without changing the resulting curve shape. - * - * \param r_pdelta: return Y difference between `bezt` and the original curve value at its X - * position. - * \return Whether the split was successful. - */ bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, @@ -2248,14 +2146,12 @@ float evaluate_fcurve_driver(PathResolvedRNA *anim_rna, return evaluate_fcurve_ex(fcu, evaltime, cvalue); } -/* Checks if the curve has valid keys, drivers or modifiers that produce an actual curve. */ bool BKE_fcurve_is_empty(FCurve *fcu) { return (fcu->totvert == 0) && (fcu->driver == NULL) && !list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE); } -/* Calculate the value of the given F-Curve at the given frame, and set its curval. */ float calculate_fcurve(PathResolvedRNA *anim_rna, FCurve *fcu, const AnimationEvalContext *anim_eval_context) diff --git a/source/blender/blenkernel/intern/fcurve_cache.c b/source/blender/blenkernel/intern/fcurve_cache.c index 8142b871edd..4f27bad5b91 100644 --- a/source/blender/blenkernel/intern/fcurve_cache.c +++ b/source/blender/blenkernel/intern/fcurve_cache.c @@ -155,11 +155,6 @@ FCurve *BKE_fcurve_pathcache_find(struct FCurvePathCache *fcache, return NULL; } -/** - * Fill in an array of F-Curve, leave NULL when not found. - * - * \return The number of F-Curves found. - */ int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache, const char *rna_path, FCurve **fcurve_result, diff --git a/source/blender/blenkernel/intern/fcurve_driver.c b/source/blender/blenkernel/intern/fcurve_driver.c index d1bf523acef..ce30f80ba65 100644 --- a/source/blender/blenkernel/intern/fcurve_driver.c +++ b/source/blender/blenkernel/intern/fcurve_driver.c @@ -29,6 +29,7 @@ #include "BLI_alloca.h" #include "BLI_expr_pylike_eval.h" +#include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string_utils.h" #include "BLI_threads.h" @@ -199,9 +200,6 @@ static float dtar_get_prop_val(ChannelDriver *driver, DriverTarget *dtar) return value; } -/** - * Same as 'dtar_get_prop_val'. but get the RNA property. - */ bool driver_get_variable_property(ChannelDriver *driver, DriverTarget *dtar, PointerRNA *r_ptr, @@ -326,7 +324,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]; @@ -422,7 +420,7 @@ static float dvar_eval_locDiff(ChannelDriver *driver, DriverVar *dvar) } } else { - /* Convert to worldspace. */ + /* Convert to world-space. */ copy_v3_v3(tmp_loc, pchan->pose_head); mul_m4_v3(ob->obmat, tmp_loc); } @@ -621,7 +619,6 @@ static void quaternion_to_angles(float quat[4], int channel) } } -/* Compute channel values for a rotational Transform Channel driver variable. */ void BKE_driver_target_matrix_to_rot_channels( float mat[4][4], int auto_order, int rotation_mode, int channel, bool angles, float r_buf[4]) { @@ -720,7 +717,6 @@ static const DriverVarTypeInfo *get_dvar_typeinfo(int type) /** \name Driver API * \{ */ -/* Perform actual freeing driver variable and remove it from the given list */ void driver_free_variable(ListBase *variables, DriverVar *dvar) { /* Sanity checks. */ @@ -745,7 +741,6 @@ void driver_free_variable(ListBase *variables, DriverVar *dvar) BLI_freelinkN(variables, dvar); } -/* Free the driver variable and do extra updates */ void driver_free_variable_ex(ChannelDriver *driver, DriverVar *dvar) { /* Remove and free the driver variable. */ @@ -755,7 +750,6 @@ void driver_free_variable_ex(ChannelDriver *driver, DriverVar *dvar) BKE_driver_invalidate_expression(driver, false, true); } -/* Copy driver variables from src_vars list to dst_vars list */ void driver_variables_copy(ListBase *dst_vars, const ListBase *src_vars) { BLI_assert(BLI_listbase_is_empty(dst_vars)); @@ -773,7 +767,6 @@ void driver_variables_copy(ListBase *dst_vars, const ListBase *src_vars) } } -/* Change the type of driver variable */ void driver_change_variable_type(DriverVar *dvar, int type) { const DriverVarTypeInfo *dvti = get_dvar_typeinfo(type); @@ -803,7 +796,6 @@ void driver_change_variable_type(DriverVar *dvar, int type) DRIVER_TARGETS_LOOPER_END; } -/* Validate driver name (after being renamed) */ void driver_variable_name_validate(DriverVar *dvar) { /* Special character blacklist */ @@ -873,7 +865,12 @@ void driver_variable_name_validate(DriverVar *dvar) } } -/* Add a new driver variable */ +void driver_variable_unique_name(DriverVar *dvar) +{ + ListBase variables = BLI_listbase_from_link((Link *)dvar); + BLI_uniquename(&variables, dvar, dvar->name, '_', offsetof(DriverVar, name), sizeof(dvar->name)); +} + DriverVar *driver_add_new_variable(ChannelDriver *driver) { DriverVar *dvar; @@ -906,7 +903,6 @@ DriverVar *driver_add_new_variable(ChannelDriver *driver) return dvar; } -/* This frees the driver itself */ void fcurve_free_driver(FCurve *fcu) { ChannelDriver *driver; @@ -939,7 +935,6 @@ void fcurve_free_driver(FCurve *fcu) fcu->driver = NULL; } -/* This makes a copy of the given driver */ ChannelDriver *fcurve_copy_driver(const ChannelDriver *driver) { ChannelDriver *ndriver; @@ -1082,7 +1077,6 @@ static bool driver_try_evaluate_simple_expr(ChannelDriver *driver, driver_evaluate_simple_expr(driver, driver_orig->expr_simple, result, time); } -/* Check if the expression in the driver conforms to the simple subset. */ bool BKE_driver_has_simple_expression(ChannelDriver *driver) { return driver_compile_simple_expr(driver) && BLI_expr_pylike_is_valid(driver->expr_simple); @@ -1109,7 +1103,6 @@ static bool python_driver_exression_depends_on_time(const char *expression) return false; } -/* Check if the expression in the driver may depend on the current frame. */ bool BKE_driver_expression_depends_on_time(ChannelDriver *driver) { if (driver->type != DRIVER_TYPE_PYTHON) { @@ -1125,7 +1118,6 @@ bool BKE_driver_expression_depends_on_time(ChannelDriver *driver) return python_driver_exression_depends_on_time(driver->expression); } -/* Reset cached compiled expression data */ void BKE_driver_invalidate_expression(ChannelDriver *driver, bool expr_changed, bool varname_changed) @@ -1152,7 +1144,6 @@ void BKE_driver_invalidate_expression(ChannelDriver *driver, /** \name Driver Evaluation * \{ */ -/* Evaluate a Driver Variable to get a value that contributes to the final */ float driver_get_variable_value(ChannelDriver *driver, DriverVar *dvar) { const DriverVarTypeInfo *dvti; @@ -1269,14 +1260,6 @@ static void evaluate_driver_python(PathResolvedRNA *anim_rna, } } -/** - * Evaluate an Channel-Driver to get a 'time' value to use - * instead of `anim_eval_context->eval_time`. - * - * - `anim_eval_context->eval_time` is the frame at which F-Curve is being evaluated. - * - Has to return a float value. - * - \a driver_orig is where we cache Python expressions, in case of COW - */ float evaluate_driver(PathResolvedRNA *anim_rna, ChannelDriver *driver, ChannelDriver *driver_orig, @@ -1309,3 +1292,5 @@ float evaluate_driver(PathResolvedRNA *anim_rna, /* Return value for driver. */ return driver->curval; } + +/** \} */ diff --git a/source/blender/blenkernel/intern/fluid.c b/source/blender/blenkernel/intern/fluid.c index e272b71acb8..0c9e352da12 100644 --- a/source/blender/blenkernel/intern/fluid.c +++ b/source/blender/blenkernel/intern/fluid.c @@ -1030,7 +1030,6 @@ static void obstacles_from_mesh(Object *coll_ob, CustomData_set_layer(&me->vdata, CD_MVERT, me->mvert); } - BKE_mesh_ensure_normals(me); mvert = me->mvert; mloop = me->mloop; looptri = BKE_mesh_runtime_looptri_ensure(me); @@ -1053,9 +1052,11 @@ static void obstacles_from_mesh(Object *coll_ob, } } - /* Transform mesh vertices to domain grid space for fast lookups */ + /* Transform mesh vertices to domain grid space for fast lookups. + * This is valid because the mesh is copied above. */ + BKE_mesh_vertex_normals_ensure(me); + float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(me); for (i = 0; i < numverts; i++) { - float n[3]; float co[3]; /* Vertex position. */ @@ -1063,11 +1064,9 @@ static void obstacles_from_mesh(Object *coll_ob, manta_pos_to_cell(fds, mvert[i].co); /* Vertex normal. */ - normal_short_to_float_v3(n, mvert[i].no); - mul_mat3_m4_v3(coll_ob->obmat, n); - mul_mat3_m4_v3(fds->imat, n); - normalize_v3(n); - normal_float_to_short_v3(mvert[i].no, n); + mul_mat3_m4_v3(coll_ob->obmat, vert_normals[i]); + mul_mat3_m4_v3(fds->imat, vert_normals[i]); + normalize_v3(vert_normals[i]); /* Vertex velocity. */ add_v3fl_v3fl_v3i(co, mvert[i].co, fds->shift); @@ -1826,6 +1825,7 @@ static void update_distances(int index, static void sample_mesh(FluidFlowSettings *ffs, const MVert *mvert, + const float (*vert_normals)[3], const MLoop *mloop, const MLoopTri *mlooptri, const MLoopUV *mloopuv, @@ -1906,7 +1906,7 @@ static void sample_mesh(FluidFlowSettings *ffs, tree_data->tree, ray_start, &nearest, tree_data->nearest_callback, tree_data) != -1) { float weights[3]; int v1, v2, v3, f_index = nearest.index; - float n1[3], n2[3], n3[3], hit_normal[3]; + float hit_normal[3]; /* Calculate barycentric weights for nearest point. */ v1 = mloop[mlooptri[f_index].tri[0]].v; @@ -1969,10 +1969,8 @@ static void sample_mesh(FluidFlowSettings *ffs, /* Apply normal directional velocity. */ if (ffs->vel_normal) { /* Interpolate vertex normal vectors to get nearest point normal. */ - normal_short_to_float_v3(n1, mvert[v1].no); - normal_short_to_float_v3(n2, mvert[v2].no); - normal_short_to_float_v3(n3, mvert[v3].no); - interp_v3_v3v3v3(hit_normal, n1, n2, n3, weights); + interp_v3_v3v3v3( + hit_normal, vert_normals[v1], vert_normals[v2], vert_normals[v3], weights); normalize_v3(hit_normal); /* Apply normal directional velocity. */ @@ -2022,6 +2020,7 @@ typedef struct EmitFromDMData { FluidFlowSettings *ffs; const MVert *mvert; + const float (*vert_normals)[3]; const MLoop *mloop; const MLoopTri *mlooptri; const MLoopUV *mloopuv; @@ -2056,6 +2055,7 @@ static void emit_from_mesh_task_cb(void *__restrict userdata, (data->ffs->behavior == FLUID_FLOW_BEHAVIOR_INFLOW)) { sample_mesh(data->ffs, data->mvert, + data->vert_normals, data->mloop, data->mlooptri, data->mloopuv, @@ -2117,7 +2117,6 @@ static void emit_from_mesh( CustomData_set_layer(&me->vdata, CD_MVERT, me->mvert); } - BKE_mesh_ensure_normals(me); mvert = me->mvert; mloop = me->mloop; mlooptri = BKE_mesh_runtime_looptri_ensure(me); @@ -2140,20 +2139,19 @@ static void emit_from_mesh( } } - /* Transform mesh vertices to domain grid space for fast lookups */ + /* Transform mesh vertices to domain grid space for fast lookups. + * This is valid because the mesh is copied above. */ + BKE_mesh_vertex_normals_ensure(me); + float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(me); for (i = 0; i < numverts; i++) { - float n[3]; - /* Vertex position. */ mul_m4_v3(flow_ob->obmat, mvert[i].co); manta_pos_to_cell(fds, mvert[i].co); /* Vertex normal. */ - normal_short_to_float_v3(n, mvert[i].no); - mul_mat3_m4_v3(flow_ob->obmat, n); - mul_mat3_m4_v3(fds->imat, n); - normalize_v3(n); - normal_float_to_short_v3(mvert[i].no, n); + mul_mat3_m4_v3(flow_ob->obmat, vert_normals[i]); + mul_mat3_m4_v3(fds->imat, vert_normals[i]); + normalize_v3(vert_normals[i]); /* Vertex velocity. */ if (ffs->flags & FLUID_FLOW_INITVELOCITY) { @@ -2193,6 +2191,7 @@ static void emit_from_mesh( .fds = fds, .ffs = ffs, .mvert = mvert, + .vert_normals = vert_normals, .mloop = mloop, .mlooptri = mlooptri, .mloopuv = mloopuv, @@ -2676,7 +2675,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; @@ -3265,8 +3264,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, MVert *mverts; MPoly *mpolys; MLoop *mloops; - short *normals, *no_s; - float no[3]; float min[3]; float max[3]; float size[3]; @@ -3285,26 +3282,23 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, const char mp_flag = mp_example.flag; int i; - int num_verts, num_normals, num_faces; + int num_verts, num_faces; if (!fds->fluid) { return NULL; } num_verts = manta_liquid_get_num_verts(fds->fluid); - num_normals = manta_liquid_get_num_normals(fds->fluid); num_faces = manta_liquid_get_num_triangles(fds->fluid); # ifdef DEBUG_PRINT /* Debugging: Print number of vertices, normals, and faces. */ - printf("num_verts: %d, num_normals: %d, num_faces: %d\n", num_verts, num_normals, num_faces); + printf("num_verts: %d, num_faces: %d\n", num_verts, num_faces); # endif if (!num_verts || !num_faces) { return NULL; } - /* Normals are per vertex, so these must match. */ - BLI_assert(num_verts == num_normals); me = BKE_mesh_new_nomain(num_verts, 0, 0, num_faces * 3, num_faces); if (!me) { @@ -3334,9 +3328,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, co_offset[1] = (fds->p0[1] + fds->p1[1]) / 2.0f; co_offset[2] = (fds->p0[2] + fds->p1[2]) / 2.0f; - /* 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; @@ -3350,7 +3341,7 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, } /* Loop for vertices and normals. */ - for (i = 0, no_s = normals; i < num_verts && i < num_normals; i++, mverts++, no_s += 3) { + for (i = 0; i < num_verts; i++, mverts++) { /* Vertices (data is normalized cube around domain origin). */ mverts->co[0] = manta_liquid_get_vertex_x_at(fds->fluid, i); @@ -3376,12 +3367,6 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, mverts->co[2]); # endif - /* Normals (data is normalized cube around domain origin). */ - no[0] = manta_liquid_get_normal_x_at(fds->fluid, i); - no[1] = manta_liquid_get_normal_y_at(fds->fluid, i); - no[2] = manta_liquid_get_normal_z_at(fds->fluid, i); - - normal_float_to_short_v3(no_s, no); # ifdef DEBUG_PRINT /* Debugging: Print coordinates of normals. */ printf("no_s[0]: %d, no_s[1]: %d, no_s[2]: %d\n", no_s[0], no_s[1], no_s[2]); @@ -3425,11 +3410,7 @@ static Mesh *create_liquid_geometry(FluidDomainSettings *fds, # endif } - BKE_mesh_ensure_normals(me); BKE_mesh_calc_edges(me, false, false); - BKE_mesh_vert_normals_apply(me, (short(*)[3])normals); - - MEM_freeN(normals); return me; } @@ -4437,8 +4418,6 @@ static void manta_smoke_calc_transparency(FluidDomainSettings *fds, ViewLayer *v } } -/* Get fluid velocity and density at given coordinates - * Returns fluid density or -1.0f if outside domain. */ float BKE_fluid_get_velocity_at(struct Object *ob, float position[3], float velocity[3]) { FluidModifierData *fmd = (FluidModifierData *)BKE_modifiers_findby_type(ob, eModifierType_Fluid); @@ -4575,10 +4554,10 @@ void BKE_fluid_particle_system_destroy(struct Object *ob, const int particle_typ } } -#endif /* WITH_FLUID */ - /** \} */ +#endif /* WITH_FLUID */ + /* -------------------------------------------------------------------- */ /** \name Public Data Access API * @@ -5090,7 +5069,7 @@ void BKE_fluid_modifier_copy(const struct FluidModifierData *fmd, copy_v4_v4(tfds->gridlines_range_color, fds->gridlines_range_color); tfds->gridlines_cell_filter = fds->gridlines_cell_filter; - /* -- Deprecated / unsed options (below)-- */ + /* -- Deprecated / unused options (below)-- */ /* pointcache options */ BKE_ptcache_free_list(&(tfds->ptcaches[0])); diff --git a/source/blender/blenkernel/intern/fmodifier.c b/source/blender/blenkernel/intern/fmodifier.c index 121927513cc..d140e70978a 100644 --- a/source/blender/blenkernel/intern/fmodifier.c +++ b/source/blender/blenkernel/intern/fmodifier.c @@ -1065,10 +1065,6 @@ static void fmods_init_typeinfo(void) fmodifiersTypeInfo[9] = &FMI_STEPPED; /* Stepped F-Curve Modifier */ } -/** - * This function should be used for getting the appropriate type-info when only - * a F-Curve modifier type is known. - */ const FModifierTypeInfo *get_fmodifier_typeinfo(const int type) { /* initialize the type-info list? */ @@ -1088,10 +1084,6 @@ const FModifierTypeInfo *get_fmodifier_typeinfo(const int type) return NULL; } -/** - * This function should always be used to get the appropriate type-info, - * as it has checks which prevent segfaults in some weird cases. - */ const FModifierTypeInfo *fmodifier_get_typeinfo(const FModifier *fcm) { /* only return typeinfo for valid modifiers */ @@ -1108,9 +1100,6 @@ const FModifierTypeInfo *fmodifier_get_typeinfo(const FModifier *fcm) /** \name F-Curve Modifier Public API * \{ */ -/** - * Add a new F-Curve Modifier to the given F-Curve of a certain type. - */ FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu) { const FModifierTypeInfo *fmi = get_fmodifier_typeinfo(type); @@ -1161,9 +1150,6 @@ FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu) return fcm; } -/** - * Make a copy of the specified F-Modifier. - */ FModifier *copy_fmodifier(const FModifier *src) { const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(src); @@ -1191,9 +1177,6 @@ FModifier *copy_fmodifier(const FModifier *src) return dst; } -/** - * Duplicate all of the F-Modifiers in the Modifier stacks. - */ void copy_fmodifiers(ListBase *dst, const ListBase *src) { FModifier *fcm, *srcfcm; @@ -1220,9 +1203,6 @@ void copy_fmodifiers(ListBase *dst, const ListBase *src) } } -/** - * Remove and free the given F-Modifier from the given stack. - */ bool remove_fmodifier(ListBase *modifiers, FModifier *fcm) { const FModifierTypeInfo *fmi = fmodifier_get_typeinfo(fcm); @@ -1263,9 +1243,6 @@ bool remove_fmodifier(ListBase *modifiers, FModifier *fcm) return false; } -/** - * Remove all of a given F-Curve's modifiers. - */ void free_fmodifiers(ListBase *modifiers) { FModifier *fcm, *fmn; @@ -1282,9 +1259,6 @@ void free_fmodifiers(ListBase *modifiers) } } -/** - * Find the active F-Modifier. - */ FModifier *find_active_fmodifier(ListBase *modifiers) { FModifier *fcm; @@ -1305,9 +1279,6 @@ FModifier *find_active_fmodifier(ListBase *modifiers) return NULL; } -/** - * Set the active F-Modifier. - */ void set_active_fmodifier(ListBase *modifiers, FModifier *fcm) { FModifier *fm; @@ -1328,12 +1299,6 @@ void set_active_fmodifier(ListBase *modifiers, FModifier *fcm) } } -/** - * Do we have any modifiers which match certain criteria. - * - * \param mtype: Type of modifier (if 0, doesn't matter). - * \param acttype: Type of action to perform (if -1, doesn't matter). - */ bool list_has_suitable_fmodifier(ListBase *modifiers, int mtype, short acttype) { FModifier *fcm; @@ -1443,19 +1408,6 @@ static float eval_fmodifier_influence(FModifier *fcm, float evaltime) return influence; } -/** - * Evaluate time modifications imposed by some F-Curve Modifiers. - * - * - This step acts as an optimization to prevent the F-Curve stack being evaluated - * several times by modifiers requesting the time be modified, as the final result - * would have required using the modified time - * - Modifiers only ever receive the unmodified time, as subsequent modifiers should be - * working on the 'global' result of the modified curve, not some localized segment, - * so \a evaltime gets set to whatever the last time-modifying modifier likes. - * - We start from the end of the stack, as only the last one matters for now. - * - * \param fcu: Can be NULL. - */ float evaluate_time_fmodifiers(FModifiersStackStorage *storage, ListBase *modifiers, FCurve *fcu, @@ -1513,10 +1465,6 @@ float evaluate_time_fmodifiers(FModifiersStackStorage *storage, return evaltime; } -/** - * Evaluates the given set of F-Curve Modifiers using the given data - * Should only be called after evaluate_time_fmodifiers() has been called. - */ void evaluate_value_fmodifiers(FModifiersStackStorage *storage, ListBase *modifiers, FCurve *fcu, @@ -1565,10 +1513,6 @@ void evaluate_value_fmodifiers(FModifiersStackStorage *storage, /* ---------- */ -/** - * Bake modifiers for given F-Curve to curve sample data, in the frame range defined - * by start and end (inclusive). - */ void fcurve_bake_modifiers(FCurve *fcu, int start, int end) { ChannelDriver *driver; diff --git a/source/blender/blenkernel/intern/freestyle.c b/source/blender/blenkernel/intern/freestyle.c index d9b3faf8623..68b7e274970 100644 --- a/source/blender/blenkernel/intern/freestyle.c +++ b/source/blender/blenkernel/intern/freestyle.c @@ -152,10 +152,6 @@ bool BKE_freestyle_module_delete(FreestyleConfig *config, FreestyleModuleConfig return true; } -/** - * Reinsert \a module_conf offset by \a direction from current position. - * \return if position of \a module_conf changed. - */ bool BKE_freestyle_module_move(FreestyleConfig *config, FreestyleModuleConfig *module_conf, int direction) diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc index 7d0537178ef..16edbc36f9c 100644 --- a/source/blender/blenkernel/intern/geometry_component_curve.cc +++ b/source/blender/blenkernel/intern/geometry_component_curve.cc @@ -14,6 +14,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "BLI_task.hh" + #include "DNA_ID_enums.h" #include "DNA_curve_types.h" @@ -28,10 +30,8 @@ using blender::fn::GMutableSpan; using blender::fn::GSpan; -using blender::fn::GVArray_For_GSpan; +using blender::fn::GVArray; using blender::fn::GVArray_GSpan; -using blender::fn::GVArrayPtr; -using blender::fn::GVMutableArray_For_GMutableSpan; /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation @@ -79,7 +79,6 @@ bool CurveComponent::has_curve() const return curve_ != nullptr; } -/* Clear the component and replace it with the new curve. */ void CurveComponent::replace(CurveEval *curve, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); @@ -130,10 +129,6 @@ void CurveComponent::ensure_owns_direct_data() } } -/** - * Create empty curve data used for rendering the spline's wire edges. - * \note See comment on #curve_for_render_ for further explanation. - */ const Curve *CurveComponent::get_curve_for_render() const { if (curve_ == nullptr) { @@ -253,15 +248,15 @@ void adapt_curve_domain_point_to_spline_impl(const CurveEval &curve, } } -static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArrayPtr varray) +static GVArray adapt_curve_domain_point_to_spline(const CurveEval &curve, GVArray varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(curve.splines().size()); - adapt_curve_domain_point_to_spline_impl<T>(curve, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_curve_domain_point_to_spline_impl<T>(curve, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -272,29 +267,29 @@ static GVArrayPtr adapt_curve_domain_point_to_spline(const CurveEval &curve, GVA * attributes. The goal is to avoid copying the spline value for every one of its control points * unless it is necessary (in that case the materialize functions will be called). */ -template<typename T> class VArray_For_SplineToPoint final : public VArray<T> { - GVArrayPtr original_varray_; +template<typename T> class VArray_For_SplineToPoint final : public VArrayImpl<T> { + GVArray original_varray_; /* Store existing data materialized if it was not already a span. This is expected * to be worth it because a single spline's value will likely be accessed many times. */ - fn::GVArray_Span<T> original_data_; + VArray_Span<T> original_data_; Array<int> offsets_; public: - VArray_For_SplineToPoint(GVArrayPtr original_varray, Array<int> offsets) - : VArray<T>(offsets.last()), + VArray_For_SplineToPoint(GVArray original_varray, Array<int> offsets) + : VArrayImpl<T>(offsets.last()), original_varray_(std::move(original_varray)), - original_data_(*original_varray_), + original_data_(original_varray_.typed<T>()), offsets_(std::move(offsets)) { } - T get_impl(const int64_t index) const final + T get(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return original_data_[indices.spline_index]; } - void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + void materialize(const IndexMask mask, MutableSpan<T> r_span) const final { const int total_size = offsets_.last(); if (mask.is_range() && mask.as_range() == IndexRange(total_size)) { @@ -315,7 +310,7 @@ template<typename T> class VArray_For_SplineToPoint final : public VArray<T> { } } - void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + void materialize_to_uninitialized(const IndexMask mask, MutableSpan<T> r_span) const final { T *dst = r_span.data(); const int total_size = offsets_.last(); @@ -338,29 +333,29 @@ template<typename T> class VArray_For_SplineToPoint final : public VArray<T> { } }; -static GVArrayPtr adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArrayPtr varray) +static GVArray adapt_curve_domain_spline_to_point(const CurveEval &curve, GVArray varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); Array<int> offsets = curve.control_point_offsets(); - new_varray = std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplineToPoint<T>>>( - offsets.last(), std::move(varray), std::move(offsets)); + new_varray = VArray<T>::template For<VArray_For_SplineToPoint<T>>(std::move(varray), + std::move(offsets)); }); return new_varray; } } // namespace blender::bke -GVArrayPtr CurveComponent::attribute_try_adapt_domain(GVArrayPtr varray, - const AttributeDomain from_domain, - const AttributeDomain to_domain) const +GVArray CurveComponent::attribute_try_adapt_domain_impl(const GVArray &varray, + const AttributeDomain from_domain, + const AttributeDomain to_domain) const { if (!varray) { return {}; } - if (varray->size() == 0) { + if (varray.is_empty()) { return {}; } if (from_domain == to_domain) { @@ -393,17 +388,109 @@ static const CurveEval *get_curve_from_component_for_read(const GeometryComponen /** \} */ +namespace blender::bke { + +/* -------------------------------------------------------------------- */ +/** \name Curve Normals Access + * \{ */ + +static void calculate_bezier_normals(const BezierSpline &spline, MutableSpan<float3> normals) +{ + Span<int> offsets = spline.control_point_offsets(); + Span<float3> evaluated_normals = spline.evaluated_normals(); + for (const int i : IndexRange(spline.size())) { + normals[i] = evaluated_normals[offsets[i]]; + } +} + +static void calculate_poly_normals(const PolySpline &spline, MutableSpan<float3> normals) +{ + normals.copy_from(spline.evaluated_normals()); +} + +/** + * Because NURBS control points are not necessarily on the path, the normal at the control points + * is not well defined, so create a temporary poly spline to find the normals. This requires extra + * copying currently, but may be more efficient in the future if attributes have some form of CoW. + */ +static void calculate_nurbs_normals(const NURBSpline &spline, MutableSpan<float3> normals) +{ + PolySpline poly_spline; + poly_spline.resize(spline.size()); + poly_spline.positions().copy_from(spline.positions()); + poly_spline.tilts().copy_from(spline.tilts()); + normals.copy_from(poly_spline.evaluated_normals()); +} + +static Array<float3> curve_normal_point_domain(const CurveEval &curve) +{ + Span<SplinePtr> splines = curve.splines(); + Array<int> offsets = curve.control_point_offsets(); + const int total_size = offsets.last(); + Array<float3> normals(total_size); + + threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) { + for (const int i : range) { + const Spline &spline = *splines[i]; + MutableSpan spline_normals{normals.as_mutable_span().slice(offsets[i], spline.size())}; + switch (splines[i]->type()) { + case Spline::Type::Bezier: + calculate_bezier_normals(static_cast<const BezierSpline &>(spline), spline_normals); + break; + case Spline::Type::Poly: + calculate_poly_normals(static_cast<const PolySpline &>(spline), spline_normals); + break; + case Spline::Type::NURBS: + calculate_nurbs_normals(static_cast<const NURBSpline &>(spline), spline_normals); + break; + } + } + }); + return normals; +} + +VArray<float3> curve_normals_varray(const CurveComponent &component, const AttributeDomain domain) +{ + const CurveEval *curve = component.get_for_read(); + if (curve == nullptr) { + return nullptr; + } + + if (domain == ATTR_DOMAIN_POINT) { + const Span<SplinePtr> splines = curve->splines(); + + /* Use a reference to evaluated normals if possible to avoid an allocation and a copy. + * This is only possible when there is only one poly spline. */ + if (splines.size() == 1 && splines.first()->type() == Spline::Type::Poly) { + const PolySpline &spline = static_cast<PolySpline &>(*splines.first()); + return VArray<float3>::ForSpan(spline.evaluated_normals()); + } + + Array<float3> normals = curve_normal_point_domain(*curve); + return VArray<float3>::ForContainer(std::move(normals)); + } + + if (domain == ATTR_DOMAIN_CURVE) { + Array<float3> point_normals = curve_normal_point_domain(*curve); + VArray<float3> varray = VArray<float3>::ForContainer(std::move(point_normals)); + return component.attribute_try_adapt_domain<float3>( + std::move(varray), ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE); + } + + return nullptr; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Builtin Spline Attributes * * Attributes with a value for every spline, stored contiguously or in every spline separately. * \{ */ -namespace blender::bke { - class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { - using AsReadAttribute = GVArrayPtr (*)(const CurveEval &data); - using AsWriteAttribute = GVMutableArrayPtr (*)(CurveEval &data); + using AsReadAttribute = GVArray (*)(const CurveEval &data); + using AsWriteAttribute = GVMutableArray (*)(CurveEval &data); const AsReadAttribute as_read_attribute_; const AsWriteAttribute as_write_attribute_; @@ -424,7 +511,7 @@ class BuiltinSplineAttributeProvider final : public BuiltinAttributeProvider { { } - GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + GVArray try_get_for_read(const GeometryComponent &component) const final { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { @@ -433,7 +520,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 {}; @@ -442,7 +529,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 @@ -483,19 +570,15 @@ static void set_spline_resolution(SplinePtr &spline, const int resolution) } } -static GVArrayPtr make_resolution_read_attribute(const CurveEval &curve) +static GVArray make_resolution_read_attribute(const CurveEval &curve) { - return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, int, get_spline_resolution>>( - curve.splines()); + return VArray<int>::ForDerivedSpan<SplinePtr, get_spline_resolution>(curve.splines()); } -static GVMutableArrayPtr make_resolution_write_attribute(CurveEval &curve) +static GVMutableArray make_resolution_write_attribute(CurveEval &curve) { - return std::make_unique<fn::GVMutableArray_For_DerivedSpan<SplinePtr, - int, - get_spline_resolution, - set_spline_resolution>>( - curve.splines()); + return VMutableArray<int>:: + ForDerivedSpan<SplinePtr, get_spline_resolution, set_spline_resolution>(curve.splines()); } static bool get_cyclic_value(const SplinePtr &spline) @@ -511,16 +594,14 @@ static void set_cyclic_value(SplinePtr &spline, const bool value) } } -static GVArrayPtr make_cyclic_read_attribute(const CurveEval &curve) +static GVArray make_cyclic_read_attribute(const CurveEval &curve) { - return std::make_unique<fn::GVArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value>>( - curve.splines()); + return VArray<bool>::ForDerivedSpan<SplinePtr, get_cyclic_value>(curve.splines()); } -static GVMutableArrayPtr make_cyclic_write_attribute(CurveEval &curve) +static GVMutableArray make_cyclic_write_attribute(CurveEval &curve) { - return std::make_unique< - fn::GVMutableArray_For_DerivedSpan<SplinePtr, bool, get_cyclic_value, set_cyclic_value>>( + return VMutableArray<bool>::ForDerivedSpan<SplinePtr, get_cyclic_value, set_cyclic_value>( curve.splines()); } @@ -535,6 +616,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, @@ -546,22 +630,40 @@ 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 { int spline_index = 0; for (const int dst_index : mask) { - while (offsets[spline_index] < dst_index) { + /* Skip splines that don't have any control points in the mask. */ + while (dst_index >= offsets[spline_index + 1]) { spline_index++; } 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, @@ -574,80 +676,159 @@ 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 { int spline_index = 0; for (const int dst_index : mask) { - while (offsets[spline_index] < dst_index) { + /* Skip splines that don't have any control points in the mask. */ + while (dst_index >= offsets[spline_index + 1]) { spline_index++; } 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]); + } } } } -/** - * Virtual array for any control point data accessed with spans and an offset array. - */ -template<typename T> class VArray_For_SplinePoints : public VArray<T> { - private: - const Array<Span<T>> data_; - Array<int> offsets_; +static GVArray 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; + case AttributeInit::Type::MoveArray: + int total_size = 0; + for (const SplinePtr &spline : splines) { + total_size += spline->size(); + } + return GVArray::ForSpan(GSpan(*bke::custom_data_type_to_cpp_type(data_type), + static_cast<const AttributeInitMove &>(initializer).data, + total_size)); + } + BLI_assert_unreachable(); + return {}; +} - public: - VArray_For_SplinePoints(Array<Span<T>> data, Array<int> offsets) - : VArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) - { +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; } - T get_impl(const int64_t index) const final - { - const PointIndices indices = lookup_point_indices(offsets_, index); - return data_[indices.spline_index][indices.point_index]; + 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; } - void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final - { - point_attribute_materialize(data_.as_span(), offsets_, mask, r_span); + /* 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; + } } - void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final - { - point_attribute_materialize_to_uninitialized(data_.as_span(), offsets_, mask, r_span); + /* 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); + + GVArray 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; +} /** * Mutable virtual array for any control point data accessed with spans and an offset array. */ -template<typename T> class VMutableArray_For_SplinePoints final : public VMutableArray<T> { +template<typename T> class VArrayImpl_For_SplinePoints final : public VMutableArrayImpl<T> { private: Array<MutableSpan<T>> data_; Array<int> offsets_; public: - VMutableArray_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets) - : VMutableArray<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) + VArrayImpl_For_SplinePoints(Array<MutableSpan<T>> data, Array<int> offsets) + : VMutableArrayImpl<T>(offsets.last()), data_(std::move(data)), offsets_(std::move(offsets)) { } - T get_impl(const int64_t index) const final + T get(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return data_[indices.spline_index][indices.point_index]; } - void set_impl(const int64_t index, T value) final + void set(const int64_t index, T value) final { const PointIndices indices = lookup_point_indices(offsets_, index); data_[indices.spline_index][indices.point_index] = value; } - void set_all_impl(Span<T> src) final + void set_all(Span<T> src) final { for (const int spline_index : data_.index_range()) { const int offset = offsets_[spline_index]; @@ -656,30 +837,29 @@ template<typename T> class VMutableArray_For_SplinePoints final : public VMutabl } } - void materialize_impl(const IndexMask mask, MutableSpan<T> r_span) const final + void materialize(const IndexMask mask, MutableSpan<T> r_span) const final { point_attribute_materialize({(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); } - void materialize_to_uninitialized_impl(const IndexMask mask, MutableSpan<T> r_span) const final + void materialize_to_uninitialized(const IndexMask mask, MutableSpan<T> r_span) const final { point_attribute_materialize_to_uninitialized( {(Span<T> *)data_.data(), data_.size()}, offsets_, mask, r_span); } }; -template<typename T> GVArrayPtr point_data_gvarray(Array<Span<T>> spans, Array<int> offsets) +template<typename T> VArray<T> point_data_varray(Array<MutableSpan<T>> spans, Array<int> offsets) { - return std::make_unique<fn::GVArray_For_EmbeddedVArray<T, VArray_For_SplinePoints<T>>>( - offsets.last(), std::move(spans), std::move(offsets)); + return VArray<T>::template For<VArrayImpl_For_SplinePoints<T>>(std::move(spans), + std::move(offsets)); } template<typename T> -GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> offsets) +VMutableArray<T> point_data_varray_mutable(Array<MutableSpan<T>> spans, Array<int> offsets) { - return std::make_unique< - fn::GVMutableArray_For_EmbeddedVMutableArray<T, VMutableArray_For_SplinePoints<T>>>( - offsets.last(), std::move(spans), std::move(offsets)); + return VMutableArray<T>::template For<VArrayImpl_For_SplinePoints<T>>(std::move(spans), + std::move(offsets)); } /** @@ -690,24 +870,24 @@ GVMutableArrayPtr point_data_gvarray(Array<MutableSpan<T>> spans, Array<int> off * \note There is no need to check the handle type to avoid changing auto handles, since * retrieving write access to the position data will mark them for recomputation anyway. */ -class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { +class VArrayImpl_For_SplinePosition final : public VMutableArrayImpl<float3> { private: MutableSpan<SplinePtr> splines_; Array<int> offsets_; public: - VMutableArray_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets) - : VMutableArray<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets)) + VArrayImpl_For_SplinePosition(MutableSpan<SplinePtr> splines, Array<int> offsets) + : VMutableArrayImpl<float3>(offsets.last()), splines_(splines), offsets_(std::move(offsets)) { } - float3 get_impl(const int64_t index) const final + float3 get(const int64_t index) const final { const PointIndices indices = lookup_point_indices(offsets_, index); return splines_[indices.spline_index]->positions()[indices.point_index]; } - void set_impl(const int64_t index, float3 value) final + void set(const int64_t index, float3 value) final { const PointIndices indices = lookup_point_indices(offsets_, index); Spline &spline = *splines_[indices.spline_index]; @@ -722,7 +902,7 @@ class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { } } - void set_all_impl(Span<float3> src) final + void set_all(Span<float3> src) final { for (const int spline_index : splines_.index_range()) { Spline &spline = *splines_[spline_index]; @@ -755,20 +935,122 @@ class VMutableArray_For_SplinePosition final : public VMutableArray<float3> { return spans; } - void materialize_impl(const IndexMask mask, MutableSpan<float3> r_span) const final + void materialize(const IndexMask mask, MutableSpan<float3> r_span) const final { Array<Span<float3>> spans = this->get_position_spans(); point_attribute_materialize(spans.as_span(), offsets_, mask, r_span); } - void materialize_to_uninitialized_impl(const IndexMask mask, - MutableSpan<float3> r_span) const final + void materialize_to_uninitialized(const IndexMask mask, MutableSpan<float3> r_span) const final { Array<Span<float3>> spans = this->get_position_spans(); point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span); } }; +class VArrayImpl_For_BezierHandles final : public VMutableArrayImpl<float3> { + private: + MutableSpan<SplinePtr> splines_; + Array<int> offsets_; + bool is_right_; + + public: + VArrayImpl_For_BezierHandles(MutableSpan<SplinePtr> splines, + Array<int> offsets, + const bool is_right) + : VMutableArrayImpl<float3>(offsets.last()), + splines_(splines), + offsets_(std::move(offsets)), + is_right_(is_right) + { + } + + float3 get(const int64_t index) const final + { + 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); + } + + void set(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(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(const IndexMask mask, MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = get_handle_spans(splines_, is_right_); + point_attribute_materialize(spans.as_span(), offsets_, mask, r_span); + } + + void materialize_to_uninitialized(const IndexMask mask, MutableSpan<float3> r_span) const final + { + Array<Span<float3>> spans = get_handle_spans(splines_, is_right_); + point_attribute_materialize_to_uninitialized(spans.as_span(), offsets_, mask, r_span); + } + + /** + * 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; + } +}; + /** * Provider for any builtin control point attribute that doesn't need * special handling like access to other arrays in the spline. @@ -781,85 +1063,140 @@ 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) { } - GVArrayPtr try_get_for_read(const GeometryComponent &component) const override + GVArray try_get_for_read(const GeometryComponent &component) const override { const CurveEval *curve = get_curve_from_component_for_read(component); if (curve == nullptr) { 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())); + return GVArray::ForSpan(get_span_(*splines.first())); } Array<int> offsets = curve->control_point_offsets(); - Array<Span<T>> spans(splines.size()); + Array<MutableSpan<T>> spans(splines.size()); for (const int i : splines.index_range()) { - spans[i] = get_span_(*splines[i]); + Span<T> span = get_span_(*splines[i]); + /* Use const-cast because the underlying virtual array implementation is shared between const + * and non const data. */ + spans[i] = MutableSpan<T>(const_cast<T *>(span.data()), span.size()); } - return point_data_gvarray(spans, offsets); + return point_data_varray(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 {GVMutableArray::ForSpan(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_varray_mutable(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; } }; @@ -873,14 +1210,16 @@ 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) { @@ -893,16 +1232,94 @@ class PositionAttributeProvider final : public BuiltinPointAttributeProvider<flo 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 {VMutableArray<float3>::For<VArrayImpl_For_SplinePosition>(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) + { + } + + GVArray 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(); + /* Use const-cast because the underlying virtual array implementation is shared between const + * and non const data. */ + return VArray<float3>::For<VArrayImpl_For_BezierHandles>( + const_cast<CurveEval *>(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_SplinePosition>>( - offsets.last(), curve->splines(), std::move(offsets)); + return {VMutableArray<float3>::For<VArrayImpl_For_BezierHandles>( + 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; } }; @@ -959,19 +1376,22 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* First check for the simpler situation when we can return a simpler span virtual array. */ if (spans.size() == 1) { - return {std::make_unique<GVArray_For_GSpan>(spans.first()), ATTR_DOMAIN_POINT}; + return {GVArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT}; } ReadAttributeLookup attribute = {}; Array<int> offsets = curve->control_point_offsets(); attribute_math::convert_to_static_type(spans[0].type(), [&](auto dummy) { using T = decltype(dummy); - Array<Span<T>> data(splines.size()); + Array<MutableSpan<T>> data(splines.size()); for (const int i : splines.index_range()) { - data[i] = spans[i].typed<T>(); + Span<T> span = spans[i].typed<T>(); + /* Use const-cast because the underlying virtual array implementation is shared between + * const and non const data. */ + data[i] = MutableSpan<T>(const_cast<T *>(span.data()), span.size()); BLI_assert(data[i].data() != nullptr); } - attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + attribute = {point_data_varray(data, offsets), ATTR_DOMAIN_POINT}; }); return attribute; } @@ -1012,7 +1432,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { /* First check for the simpler situation when we can return a simpler span virtual array. */ if (spans.size() == 1) { - return {std::make_unique<GVMutableArray_For_GMutableSpan>(spans.first()), ATTR_DOMAIN_POINT}; + return {GVMutableArray::ForSpan(spans.first()), ATTR_DOMAIN_POINT}; } WriteAttributeLookup attribute = {}; @@ -1024,46 +1444,14 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider { data[i] = spans[i].typed<T>(); BLI_assert(data[i].data() != nullptr); } - attribute = {point_data_gvarray(data, offsets), ATTR_DOMAIN_POINT}; + attribute = {point_data_varray_mutable(data, offsets), ATTR_DOMAIN_POINT}; }); return attribute; } bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) 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_id); - } - return layer_freed; - } - - static GVArrayPtr varray_from_initializer(const AttributeInit &initializer, - const CustomDataType data_type, - const int total_size) - { - 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, @@ -1076,55 +1464,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_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 = this->try_get_for_write(component, attribute_id); - /* 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, @@ -1196,27 +1536,51 @@ 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 const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_providers() const @@ -1225,5 +1589,3 @@ const blender::bke::ComponentAttributeProviders *CurveComponent::get_attribute_p blender::bke::create_attribute_providers_for_curve(); return &providers; } - -/** \} */ diff --git a/source/blender/blenkernel/intern/geometry_component_instances.cc b/source/blender/blenkernel/intern/geometry_component_instances.cc index 4204d62e1a7..b411c793298 100644 --- a/source/blender/blenkernel/intern/geometry_component_instances.cc +++ b/source/blender/blenkernel/intern/geometry_component_instances.cc @@ -17,6 +17,7 @@ #include <mutex> #include "BLI_float4x4.hh" +#include "BLI_index_mask.hh" #include "BLI_map.hh" #include "BLI_rand.hh" #include "BLI_set.hh" @@ -26,17 +27,25 @@ #include "DNA_collection_types.h" +#include "BKE_attribute_access.hh" +#include "BKE_attribute_math.hh" #include "BKE_geometry_set.hh" #include "BKE_geometry_set_instances.hh" #include "attribute_access_intern.hh" +#include "FN_cpp_type_make.hh" + using blender::float4x4; +using blender::IndexMask; using blender::Map; using blender::MutableSpan; using blender::Set; using blender::Span; using blender::VectorSet; +using blender::fn::GSpan; + +MAKE_CPP_TYPE(InstanceReference, InstanceReference, CPPTypeFlags::None) /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation @@ -51,8 +60,8 @@ GeometryComponent *InstancesComponent::copy() const InstancesComponent *new_component = new InstancesComponent(); new_component->instance_reference_handles_ = instance_reference_handles_; new_component->instance_transforms_ = instance_transforms_; - new_component->instance_ids_ = instance_ids_; new_component->references_ = references_; + new_component->attributes_ = attributes_; return new_component; } @@ -60,40 +69,31 @@ void InstancesComponent::reserve(int min_capacity) { instance_reference_handles_.reserve(min_capacity); instance_transforms_.reserve(min_capacity); - instance_ids_.reserve(min_capacity); + attributes_.reallocate(min_capacity); } -/** - * Resize the transform, handles, and ID vectors to the specified capacity. - * - * \note This function should be used carefully, only when it's guaranteed - * that the data will be filled. - */ void InstancesComponent::resize(int capacity) { instance_reference_handles_.resize(capacity); instance_transforms_.resize(capacity); - instance_ids_.resize(capacity); + attributes_.reallocate(capacity); } void InstancesComponent::clear() { instance_reference_handles_.clear(); instance_transforms_.clear(); - instance_ids_.clear(); - + attributes_.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); + attributes_.reallocate(this->instances_amount()); } blender::Span<int> InstancesComponent::instance_reference_handles() const @@ -115,20 +115,6 @@ blender::Span<blender::float4x4> InstancesComponent::instance_transforms() const return instance_transforms_; } -blender::MutableSpan<int> InstancesComponent::instance_ids() -{ - return instance_ids_; -} -blender::Span<int> InstancesComponent::instance_ids() const -{ - return instance_ids_; -} - -/** - * 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 @@ -140,11 +126,6 @@ GeometrySet &InstancesComponent::geometry_set_from_reference(const int reference 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(const InstanceReference &reference) { return references_.index_of_or_add_as(reference); @@ -155,6 +136,62 @@ blender::Span<InstanceReference> InstancesComponent::references() const return references_; } +template<typename T> +static void copy_data_based_on_mask(Span<T> src, MutableSpan<T> dst, IndexMask mask) +{ + BLI_assert(src.data() != dst.data()); + using namespace blender; + threading::parallel_for(mask.index_range(), 1024, [&](IndexRange range) { + for (const int i : range) { + dst[i] = src[mask[i]]; + } + }); +} + +void InstancesComponent::remove_instances(const IndexMask mask) +{ + using namespace blender; + if (mask.is_range() && mask.as_range().start() == 0) { + /* Deleting from the end of the array can be much faster since no data has to be shifted. */ + this->resize(mask.size()); + this->remove_unused_references(); + return; + } + + Vector<int> new_handles(mask.size()); + copy_data_based_on_mask<int>(this->instance_reference_handles(), new_handles, mask); + instance_reference_handles_ = std::move(new_handles); + Vector<float4x4> new_transforms(mask.size()); + copy_data_based_on_mask<float4x4>(this->instance_transforms(), new_transforms, mask); + instance_transforms_ = std::move(new_transforms); + + const bke::CustomDataAttributes &src_attributes = attributes_; + + bke::CustomDataAttributes dst_attributes; + dst_attributes.reallocate(mask.size()); + + src_attributes.foreach_attribute( + [&](const bke::AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (!id.should_be_kept()) { + return true; + } + + GSpan src = *src_attributes.get_for_read(id); + dst_attributes.create(id, meta_data.data_type); + fn::GMutableSpan dst = *dst_attributes.get_for_write(id); + + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + copy_data_based_on_mask<T>(src.typed<T>(), dst.typed<T>(), mask); + }); + return true; + }, + ATTR_DOMAIN_INSTANCE); + + attributes_ = std::move(dst_attributes); + this->remove_unused_references(); +} + void InstancesComponent::remove_unused_references() { using namespace blender; @@ -327,20 +364,40 @@ 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_); + std::optional<GSpan> instance_ids_gspan = attributes_.get_for_read("id"); + if (instance_ids_gspan) { + Span<int> instance_ids = instance_ids_gspan->typed<int>(); + if (almost_unique_ids_.size() != instance_ids.size()) { + almost_unique_ids_ = generate_unique_instance_ids(instance_ids); + } + } + else { + almost_unique_ids_.reinitialize(this->instances_amount()); + for (const int i : almost_unique_ids_.index_range()) { + almost_unique_ids_[i] = i; + } } return almost_unique_ids_; } int InstancesComponent::attribute_domain_size(const AttributeDomain domain) const { - if (domain != ATTR_DOMAIN_POINT) { + if (domain != ATTR_DOMAIN_INSTANCE) { return 0; } return this->instances_amount(); } +blender::bke::CustomDataAttributes &InstancesComponent::attributes() +{ + return this->attributes_; +} + +const blender::bke::CustomDataAttributes &InstancesComponent::attributes() const +{ + return this->attributes_; +} + namespace blender::bke { static float3 get_transform_position(const float4x4 &transform) @@ -357,28 +414,26 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider public: InstancePositionAttributeProvider() : BuiltinAttributeProvider( - "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, NonCreatable, Writable, NonDeletable) + "position", ATTR_DOMAIN_INSTANCE, CD_PROP_FLOAT3, NonCreatable, Writable, NonDeletable) { } - GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + GVArray 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); + return VArray<float3>::ForDerivedSpan<float4x4, get_transform_position>(transforms); } - GVMutableArrayPtr try_get_for_write(GeometryComponent &component) const final + 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); + return {VMutableArray<float3>::ForDerivedSpan<float4x4, + get_transform_position, + set_transform_position>(transforms), + domain_}; } bool try_delete(GeometryComponent &UNUSED(component)) const final @@ -398,11 +453,54 @@ class InstancePositionAttributeProvider final : public BuiltinAttributeProvider } }; +template<typename T> +static GVArray make_array_read_attribute(const void *data, const int domain_size) +{ + return VArray<T>::ForSpan(Span<T>((const T *)data, domain_size)); +} + +template<typename T> +static GVMutableArray make_array_write_attribute(void *data, const int domain_size) +{ + return VMutableArray<T>::ForSpan(MutableSpan<T>((T *)data, domain_size)); +} + static ComponentAttributeProviders create_attribute_providers_for_instances() { static InstancePositionAttributeProvider position; - - return ComponentAttributeProviders({&position}, {}); + static CustomDataAccessInfo instance_custom_data_access = { + [](GeometryComponent &component) -> CustomData * { + InstancesComponent &inst = static_cast<InstancesComponent &>(component); + return &inst.attributes().data; + }, + [](const GeometryComponent &component) -> const CustomData * { + const InstancesComponent &inst = static_cast<const InstancesComponent &>(component); + return &inst.attributes().data; + }, + nullptr}; + + /** + * IDs of the instances. They are used for consistency over multiple frames for things like + * 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. + */ + static BuiltinCustomDataLayerProvider id("id", + ATTR_DOMAIN_INSTANCE, + CD_PROP_INT32, + CD_PROP_INT32, + BuiltinAttributeProvider::Creatable, + BuiltinAttributeProvider::Writable, + BuiltinAttributeProvider::Deletable, + instance_custom_data_access, + make_array_read_attribute<int>, + make_array_write_attribute<int>, + nullptr); + + static CustomDataAttributeProvider instance_custom_data(ATTR_DOMAIN_INSTANCE, + instance_custom_data_access); + + return ComponentAttributeProviders({&position, &id}, {&instance_custom_data}); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 0c98aa5551b..2509448d8aa 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -15,6 +15,7 @@ */ #include "BLI_listbase.h" +#include "BLI_task.hh" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -29,11 +30,8 @@ #include "attribute_access_intern.hh" -/* Can't include BKE_object_deform.h right now, due to an enum forward declaration. */ extern "C" MDeformVert *BKE_object_defgroup_data_create(ID *id); -using blender::fn::GVArray; - /* -------------------------------------------------------------------- */ /** \name Geometry Component Implementation * \{ */ @@ -73,7 +71,6 @@ bool MeshComponent::has_mesh() const return mesh_ != nullptr; } -/* Clear the component and replace it with the new mesh. */ void MeshComponent::replace(Mesh *mesh, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); @@ -82,8 +79,6 @@ void MeshComponent::replace(Mesh *mesh, GeometryOwnershipType ownership) 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() { BLI_assert(this->is_mutable()); @@ -92,15 +87,11 @@ Mesh *MeshComponent::release() return mesh; } -/* 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 { return mesh_; } -/* Get the mesh from this component. This method can only be used when the component is mutable, - * i.e. it is not shared. The returned mesh can be modified. No ownership is transferred. */ Mesh *MeshComponent::get_for_write() { BLI_assert(this->is_mutable()); @@ -133,6 +124,61 @@ void MeshComponent::ensure_owns_direct_data() /** \} */ /* -------------------------------------------------------------------- */ +/** \name Mesh Normals Field Input + * \{ */ + +namespace blender::bke { + +VArray<float3> mesh_normals_varray(const MeshComponent &mesh_component, + const Mesh &mesh, + const IndexMask mask, + const AttributeDomain domain) +{ + switch (domain) { + case ATTR_DOMAIN_FACE: { + return VArray<float3>::ForSpan( + {(float3 *)BKE_mesh_poly_normals_ensure(&mesh), mesh.totpoly}); + } + case ATTR_DOMAIN_POINT: { + return VArray<float3>::ForSpan( + {(float3 *)BKE_mesh_vertex_normals_ensure(&mesh), mesh.totvert}); + } + case ATTR_DOMAIN_EDGE: { + /* In this case, start with vertex normals and convert to the edge domain, since the + * conversion from edges to vertices is very simple. Use "manual" domain interpolation + * instead of the GeometryComponent API to avoid calculating unnecessary values and to + * allow normalizing the result more simply. */ + Span<float3> vert_normals{(float3 *)BKE_mesh_vertex_normals_ensure(&mesh), mesh.totvert}; + Array<float3> edge_normals(mask.min_array_size()); + Span<MEdge> edges{mesh.medge, mesh.totedge}; + for (const int i : mask) { + const MEdge &edge = edges[i]; + edge_normals[i] = math::normalize( + math::interpolate(vert_normals[edge.v1], vert_normals[edge.v2], 0.5f)); + } + + return VArray<float3>::ForContainer(std::move(edge_normals)); + } + case ATTR_DOMAIN_CORNER: { + /* The normals on corners are just the mesh's face normals, so start with the face normal + * array and copy the face normal for each of its corners. In this case using the mesh + * component's generic domain interpolation is fine, the data will still be normalized, + * since the face normal is just copied to every corner. */ + return mesh_component.attribute_try_adapt_domain( + VArray<float3>::ForSpan({(float3 *)BKE_mesh_poly_normals_ensure(&mesh), mesh.totpoly}), + ATTR_DOMAIN_FACE, + ATTR_DOMAIN_CORNER); + } + default: + return {}; + } +} + +} // namespace blender::bke + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Attribute Access * \{ */ @@ -203,17 +249,17 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, } } -static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { /* We compute all interpolated values at once, because for this interpolation, one has to * iterate over all loops anyway. */ Array<T> values(mesh.totvert); - adapt_mesh_domain_corner_to_point_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_corner_to_point_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -221,89 +267,54 @@ static GVArrayPtr adapt_mesh_domain_corner_to_point(const Mesh &mesh, GVArrayPtr /** * 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, - MutableSpan<T> r_values) -{ - BLI_assert(r_values.size() == mesh.totloop); - - for (const int loop_index : IndexRange(mesh.totloop)) { - const int vertex_index = mesh.mloop[loop_index].v; - r_values[loop_index] = old_values[vertex_index]; - } -} - -static GVArrayPtr adapt_mesh_domain_point_to_corner(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_point_to_corner(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); - 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)); + new_varray = VArray<T>::ForFunc(mesh.totloop, + [mesh, varray = varray.typed<T>()](const int64_t loop_index) { + const int vertex_index = mesh.mloop[loop_index].v; + return varray[vertex_index]; + }); }); return new_varray; } -/** - * \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_corner_to_face_impl(const Mesh &mesh, - const VArray<T> &old_values, - MutableSpan<T> r_values) +static GVArray adapt_mesh_domain_corner_to_face(const Mesh &mesh, const GVArray &varray) { - BLI_assert(r_values.size() == mesh.totpoly); - attribute_math::DefaultMixer<T> mixer(r_values); - - 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 T value = old_values[loop_index]; - mixer.mix_in(poly_index, value); - } - } - - 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; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { - Array<T> values(mesh.totpoly); - adapt_mesh_domain_corner_to_face_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + if constexpr (std::is_same_v<T, bool>) { + new_varray = VArray<T>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<bool>()](const int face_index) { + /* A face is selected if all of its corners were selected. */ + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + if (!varray[loop_index]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray<T>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<T>()](const int face_index) { + T return_value; + attribute_math::DefaultMixer<T> mixer({&return_value, 1}); + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const T value = varray[loop_index]; + mixer.mix_in(0, value); + } + mixer.finalize(); + return return_value; + }); + } } }); return new_varray; @@ -361,22 +372,24 @@ void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh, } /* 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; + threading::parallel_for(IndexRange(mesh.totedge), 2048, [&](const IndexRange range) { + for (const int edge_index : range) { + if (loose_edges[edge_index]) { + r_values[edge_index] = false; + } } - } + }); } -static GVArrayPtr adapt_mesh_domain_corner_to_edge(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_corner_to_edge(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); - adapt_mesh_domain_corner_to_edge_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_corner_to_edge_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -424,15 +437,15 @@ void adapt_mesh_domain_face_to_point_impl(const Mesh &mesh, } } -static GVArrayPtr adapt_mesh_domain_face_to_point(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_face_to_point(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); - adapt_mesh_domain_face_to_point_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_face_to_point_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -446,22 +459,24 @@ void adapt_mesh_domain_face_to_corner_impl(const Mesh &mesh, { BLI_assert(r_values.size() == mesh.totloop); - for (const int poly_index : IndexRange(mesh.totpoly)) { - const MPoly &poly = mesh.mpoly[poly_index]; - MutableSpan<T> poly_corner_values = r_values.slice(poly.loopstart, poly.totloop); - poly_corner_values.fill(old_values[poly_index]); - } + threading::parallel_for(IndexRange(mesh.totpoly), 1024, [&](const IndexRange range) { + for (const int poly_index : range) { + const MPoly &poly = mesh.mpoly[poly_index]; + MutableSpan<T> poly_corner_values = r_values.slice(poly.loopstart, poly.totloop); + poly_corner_values.fill(old_values[poly_index]); + } + }); } -static GVArrayPtr adapt_mesh_domain_face_to_corner(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_face_to_corner(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); - adapt_mesh_domain_face_to_corner_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_face_to_corner_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -507,125 +522,86 @@ void adapt_mesh_domain_face_to_edge_impl(const Mesh &mesh, } } -static GVArrayPtr adapt_mesh_domain_face_to_edge(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_face_to_edge(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totedge); - adapt_mesh_domain_face_to_edge_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_face_to_edge_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; } -/** - * \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_face_impl(const Mesh &mesh, - const VArray<T> &old_values, - MutableSpan<T> r_values) -{ - BLI_assert(r_values.size() == mesh.totpoly); - attribute_math::DefaultMixer<T> mixer(r_values); - - 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 point_index = loop.v; - mixer.mix_in(poly_index, old_values[point_index]); - } - } - 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) +static GVArray adapt_mesh_domain_point_to_face(const Mesh &mesh, const GVArray &varray) { - 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; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { - Array<T> values(mesh.totpoly); - adapt_mesh_domain_point_to_face_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + if constexpr (std::is_same_v<T, bool>) { + new_varray = VArray<T>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<bool>()](const int face_index) { + /* A face is selected if all of its vertices were selected. */ + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + if (!varray[loop.v]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray<T>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<T>()](const int face_index) { + T return_value; + attribute_math::DefaultMixer<T> mixer({&return_value, 1}); + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const T value = varray[loop.v]; + mixer.mix_in(0, value); + } + mixer.finalize(); + return return_value; + }); + } } }); return new_varray; } -/** - * \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_edge_impl(const Mesh &mesh, - const VArray<T> &old_values, - MutableSpan<T> r_values) -{ - BLI_assert(r_values.size() == mesh.totedge); - attribute_math::DefaultMixer<T> mixer(r_values); - - for (const int edge_index : IndexRange(mesh.totedge)) { - const MEdge &edge = mesh.medge[edge_index]; - mixer.mix_in(edge_index, old_values[edge.v1]); - mixer.mix_in(edge_index, old_values[edge.v2]); - } - - 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) +static GVArray adapt_mesh_domain_point_to_edge(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { - Array<T> values(mesh.totedge); - adapt_mesh_domain_point_to_edge_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + if constexpr (std::is_same_v<T, bool>) { + /* An edge is selected if both of its vertices were selected. */ + new_varray = VArray<bool>::ForFunc( + mesh.totedge, [mesh, varray = varray.typed<bool>()](const int edge_index) { + const MEdge &edge = mesh.medge[edge_index]; + return varray[edge.v1] && varray[edge.v2]; + }); + } + else { + new_varray = VArray<T>::ForFunc( + mesh.totedge, [mesh, varray = varray.typed<T>()](const int edge_index) { + T return_value; + attribute_math::DefaultMixer<T> mixer({&return_value, 1}); + const MEdge &edge = mesh.medge[edge_index]; + mixer.mix_in(0, varray[edge.v1]); + mixer.mix_in(0, varray[edge.v2]); + mixer.finalize(); + return return_value; + }); + } } }); return new_varray; @@ -678,15 +654,15 @@ void adapt_mesh_domain_edge_to_corner_impl(const Mesh &mesh, } } -static GVArrayPtr adapt_mesh_domain_edge_to_corner(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_edge_to_corner(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totloop); - adapt_mesh_domain_edge_to_corner_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_edge_to_corner_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; @@ -728,75 +704,55 @@ void adapt_mesh_domain_edge_to_point_impl(const Mesh &mesh, } } -static GVArrayPtr adapt_mesh_domain_edge_to_point(const Mesh &mesh, GVArrayPtr varray) +static GVArray adapt_mesh_domain_edge_to_point(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { Array<T> values(mesh.totvert); - adapt_mesh_domain_edge_to_point_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + adapt_mesh_domain_edge_to_point_impl<T>(mesh, varray.typed<T>(), values); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); return new_varray; } -/** - * \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_edge_to_face_impl(const Mesh &mesh, - const VArray<T> &old_values, - MutableSpan<T> r_values) -{ - BLI_assert(r_values.size() == mesh.totpoly); - attribute_math::DefaultMixer<T> mixer(r_values); - - 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]; - mixer.mix_in(poly_index, old_values[loop.e]); - } - } - - 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) +static GVArray adapt_mesh_domain_edge_to_face(const Mesh &mesh, const GVArray &varray) { - GVArrayPtr new_varray; - attribute_math::convert_to_static_type(varray->type(), [&](auto dummy) { + GVArray new_varray; + attribute_math::convert_to_static_type(varray.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) { - Array<T> values(mesh.totpoly); - adapt_mesh_domain_edge_to_face_impl<T>(mesh, varray->typed<T>(), values); - new_varray = std::make_unique<fn::GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + if constexpr (std::is_same_v<T, bool>) { + /* A face is selected if all of its edges are selected. */ + new_varray = VArray<bool>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<T>()](const int face_index) { + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + if (!varray[loop.e]) { + return false; + } + } + return true; + }); + } + else { + new_varray = VArray<T>::ForFunc( + mesh.totpoly, [mesh, varray = varray.typed<T>()](const int face_index) { + T return_value; + attribute_math::DefaultMixer<T> mixer({&return_value, 1}); + const MPoly &poly = mesh.mpoly[face_index]; + for (const int loop_index : IndexRange(poly.loopstart, poly.totloop)) { + const MLoop &loop = mesh.mloop[loop_index]; + const T value = varray[loop.e]; + mixer.mix_in(0, value); + } + mixer.finalize(); + return return_value; + }); + } } }); return new_varray; @@ -804,15 +760,15 @@ static GVArrayPtr adapt_mesh_domain_edge_to_face(const Mesh &mesh, GVArrayPtr va } // namespace blender::bke -blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( - blender::fn::GVArrayPtr varray, +blender::fn::GVArray MeshComponent::attribute_try_adapt_domain_impl( + const blender::fn::GVArray &varray, const AttributeDomain from_domain, const AttributeDomain to_domain) const { if (!varray) { return {}; } - if (varray->size() == 0) { + if (varray.size() == 0) { return {}; } if (from_domain == to_domain) { @@ -823,11 +779,11 @@ blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( case ATTR_DOMAIN_CORNER: { switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_corner_to_point(*mesh_, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_corner_to_face(*mesh_, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_corner_to_edge(*mesh_, varray); default: break; } @@ -836,11 +792,11 @@ blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( case ATTR_DOMAIN_POINT: { switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_point_to_corner(*mesh_, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_point_to_face(*mesh_, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_point_to_edge(*mesh_, varray); default: break; } @@ -849,11 +805,11 @@ blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( case ATTR_DOMAIN_FACE: { switch (to_domain) { case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_face_to_point(*mesh_, varray); case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_face_to_corner(*mesh_, varray); case ATTR_DOMAIN_EDGE: - return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_face_to_edge(*mesh_, varray); default: break; } @@ -862,11 +818,11 @@ blender::fn::GVArrayPtr MeshComponent::attribute_try_adapt_domain( case ATTR_DOMAIN_EDGE: { switch (to_domain) { case ATTR_DOMAIN_CORNER: - return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_edge_to_corner(*mesh_, varray); case ATTR_DOMAIN_POINT: - return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_edge_to_point(*mesh_, varray); case ATTR_DOMAIN_FACE: - return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, std::move(varray)); + return blender::bke::adapt_mesh_domain_edge_to_face(*mesh_, varray); default: break; } @@ -896,9 +852,9 @@ static const Mesh *get_mesh_from_component_for_read(const GeometryComponent &com namespace blender::bke { template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &)> -static GVArrayPtr make_derived_read_attribute(const void *data, const int domain_size) +static GVArray make_derived_read_attribute(const void *data, const int domain_size) { - return std::make_unique<fn::GVArray_For_DerivedSpan<StructT, ElemT, GetFunc>>( + return VArray<ElemT>::template ForDerivedSpan<StructT, GetFunc>( Span<StructT>((const StructT *)data, domain_size)); } @@ -906,12 +862,24 @@ template<typename StructT, typename ElemT, ElemT (*GetFunc)(const StructT &), void (*SetFunc)(StructT &, ElemT)> -static GVMutableArrayPtr make_derived_write_attribute(void *data, const int domain_size) +static GVMutableArray make_derived_write_attribute(void *data, const int domain_size) { - return std::make_unique<fn::GVMutableArray_For_DerivedSpan<StructT, ElemT, GetFunc, SetFunc>>( + return VMutableArray<ElemT>::template ForDerivedSpan<StructT, GetFunc, SetFunc>( MutableSpan<StructT>((StructT *)data, domain_size)); } +template<typename T> +static GVArray make_array_read_attribute(const void *data, const int domain_size) +{ + return VArray<T>::ForSpan(Span<T>((const T *)data, domain_size)); +} + +template<typename T> +static GVMutableArray make_array_write_attribute(void *data, const int domain_size) +{ + return VMutableArray<T>::ForSpan(MutableSpan<T>((T *)data, domain_size)); +} + static float3 get_vertex_position(const MVert &vert) { return float3(vert.co); @@ -986,57 +954,36 @@ static void set_crease(MEdge &edge, float value) edge.crease = round_fl_to_uchar_clamp(value * 255.0f); } -class VMutableArray_For_VertexWeights final : public VMutableArray<float> { +class VArrayImpl_For_VertexWeights final : public VMutableArrayImpl<float> { private: MDeformVert *dverts_; const int dvert_index_; public: - VMutableArray_For_VertexWeights(MDeformVert *dverts, const int totvert, const int dvert_index) - : VMutableArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) - { - } - - float get_impl(const int64_t index) const override - { - return get_internal(dverts_, dvert_index_, index); - } - - void set_impl(const int64_t index, const float value) override + VArrayImpl_For_VertexWeights(MDeformVert *dverts, const int totvert, const int dvert_index) + : VMutableArrayImpl<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) { - MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); - weight->weight = value; } - static float get_internal(const MDeformVert *dverts, const int dvert_index, const int64_t index) + float get(const int64_t index) const override { - if (dverts == nullptr) { + if (dverts_ == nullptr) { return 0.0f; } - const MDeformVert &dvert = dverts[index]; + const MDeformVert &dvert = dverts_[index]; for (const MDeformWeight &weight : Span(dvert.dw, dvert.totweight)) { - if (weight.def_nr == dvert_index) { + if (weight.def_nr == dvert_index_) { return weight.weight; } } return 0.0f; - } -}; - -class VArray_For_VertexWeights final : public VArray<float> { - private: - const MDeformVert *dverts_; - const int dvert_index_; - - public: - VArray_For_VertexWeights(const MDeformVert *dverts, const int totvert, const int dvert_index) - : VArray<float>(totvert), dverts_(dverts), dvert_index_(dvert_index) - { + ; } - float get_impl(const int64_t index) const override + void set(const int64_t index, const float value) override { - return VMutableArray_For_VertexWeights::get_internal(dverts_, dvert_index_, index); + MDeformWeight *weight = BKE_defvert_ensure_index(&dverts_[index], dvert_index_); + weight->weight = value; } }; @@ -1065,12 +1012,10 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } 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), - ATTR_DOMAIN_POINT}; + return {VArray<float>::ForSingle(default_value, mesh->totvert), ATTR_DOMAIN_POINT}; } - return {std::make_unique<fn::GVArray_For_EmbeddedVArray<float, VArray_For_VertexWeights>>( - mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), + return {VArray<float>::For<VArrayImpl_For_VertexWeights>( + mesh->dvert, mesh->totvert, vertex_group_index), ATTR_DOMAIN_POINT}; } @@ -1101,11 +1046,9 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { mesh->dvert = (MDeformVert *)CustomData_duplicate_referenced_layer( &mesh->vdata, CD_MDEFORMVERT, mesh->totvert); } - return { - std::make_unique< - fn::GVMutableArray_For_EmbeddedVMutableArray<float, VMutableArray_For_VertexWeights>>( - mesh->totvert, mesh->dvert, mesh->totvert, vertex_group_index), - ATTR_DOMAIN_POINT}; + return {VMutableArray<float>::For<VArrayImpl_For_VertexWeights>( + mesh->dvert, mesh->totvert, vertex_group_index), + ATTR_DOMAIN_POINT}; } bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final @@ -1121,16 +1064,19 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { } 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) { + + int index; + bDeformGroup *group; + if (!BKE_id_defgroup_name_find(&mesh->id, name.c_str(), &index, &group)) { return false; } + BLI_remlink(&mesh->vertex_group_names, group); + MEM_freeN(group); if (mesh->dvert == nullptr) { return true; } for (MDeformVert &dvert : MutableSpan(mesh->dvert, mesh->totvert)) { - MDeformWeight *weight = BKE_defvert_find_index(&dvert, vertex_group_index); + MDeformWeight *weight = BKE_defvert_find_index(&dvert, index); BKE_defvert_remove_group(&dvert, weight); } return true; @@ -1171,33 +1117,17 @@ class NormalAttributeProvider final : public BuiltinAttributeProvider { { } - GVArrayPtr try_get_for_read(const GeometryComponent &component) const final + GVArray try_get_for_read(const GeometryComponent &component) const final { const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); const Mesh *mesh = mesh_component.get_for_read(); - if (mesh == nullptr) { + if (mesh == nullptr || mesh->totpoly == 0) { return {}; } - - /* Use existing normals if possible. */ - if (!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL) && - CustomData_has_layer(&mesh->pdata, CD_NORMAL)) { - const void *data = CustomData_get_layer(&mesh->pdata, CD_NORMAL); - - return std::make_unique<fn::GVArray_For_Span<float3>>( - Span<float3>((const float3 *)data, mesh->totpoly)); - } - - Array<float3> normals(mesh->totpoly); - for (const int i : IndexRange(mesh->totpoly)) { - const MPoly *poly = &mesh->mpoly[i]; - BKE_mesh_calc_poly_normal(poly, &mesh->mloop[poly->loopstart], mesh->mvert, normals[i]); - } - - return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals)); + return VArray<float3>::ForSpan({(float3 *)BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly}); } - GVMutableArrayPtr try_get_for_write(GeometryComponent &UNUSED(component)) const final + WriteAttributeLookup try_get_for_write(GeometryComponent &UNUSED(component)) const final { return {}; } @@ -1274,6 +1204,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, @@ -1335,14 +1277,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..80c09a7ed4a 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -62,7 +62,6 @@ bool PointCloudComponent::has_pointcloud() const return pointcloud_ != nullptr; } -/* Clear the component and replace it with the new point cloud. */ void PointCloudComponent::replace(PointCloud *pointcloud, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); @@ -71,8 +70,6 @@ void PointCloudComponent::replace(PointCloud *pointcloud, GeometryOwnershipType ownership_ = ownership; } -/* Return the point cloud and clear the component. The caller takes over responsibility for freeing - * the point cloud (if the component was responsible before). */ PointCloud *PointCloudComponent::release() { BLI_assert(this->is_mutable()); @@ -81,17 +78,11 @@ PointCloud *PointCloudComponent::release() return pointcloud; } -/* Get the point cloud from this component. This method can be used by multiple threads at the same - * time. Therefore, the returned point cloud should not be modified. No ownership is transferred. - */ const PointCloud *PointCloudComponent::get_for_read() const { return pointcloud_; } -/* Get the point cloud from this component. This method can only be used when the component is - * mutable, i.e. it is not shared. The returned point cloud can be modified. No ownership is - * transferred. */ PointCloud *PointCloudComponent::get_for_write() { BLI_assert(this->is_mutable()); @@ -141,16 +132,15 @@ int PointCloudComponent::attribute_domain_size(const AttributeDomain domain) con namespace blender::bke { template<typename T> -static GVArrayPtr make_array_read_attribute(const void *data, const int domain_size) +static GVArray 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)); + return VArray<T>::ForSpan(Span<T>((const T *)data, domain_size)); } template<typename T> -static GVMutableArrayPtr make_array_write_attribute(void *data, const int domain_size) +static GVMutableArray 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)); + return VMutableArray<T>::ForSpan(MutableSpan<T>((T *)data, domain_size)); } /** @@ -202,8 +192,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_component_volume.cc b/source/blender/blenkernel/intern/geometry_component_volume.cc index 94ed07a63de..e9874153b4c 100644 --- a/source/blender/blenkernel/intern/geometry_component_volume.cc +++ b/source/blender/blenkernel/intern/geometry_component_volume.cc @@ -59,7 +59,6 @@ bool VolumeComponent::has_volume() const return volume_ != nullptr; } -/* Clear the component and replace it with the new volume. */ void VolumeComponent::replace(Volume *volume, GeometryOwnershipType ownership) { BLI_assert(this->is_mutable()); @@ -68,8 +67,6 @@ void VolumeComponent::replace(Volume *volume, GeometryOwnershipType ownership) ownership_ = ownership; } -/* Return the volume and clear the component. The caller takes over responsibility for freeing the - * volume (if the component was responsible before). */ Volume *VolumeComponent::release() { BLI_assert(this->is_mutable()); @@ -78,15 +75,11 @@ Volume *VolumeComponent::release() return volume; } -/* Get the volume from this component. This method can be used by multiple threads at the same - * time. Therefore, the returned volume should not be modified. No ownership is transferred. */ const Volume *VolumeComponent::get_for_read() const { return volume_; } -/* Get the volume from this component. This method can only be used when the component is mutable, - * i.e. it is not shared. The returned volume can be modified. No ownership is transferred. */ Volume *VolumeComponent::get_for_write() { BLI_assert(this->is_mutable()); diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 0aac6ae3adf..c1e386c626b 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -17,6 +17,8 @@ #include "BLI_map.hh" #include "BLI_task.hh" +#include "BLT_translation.h" + #include "BKE_attribute.h" #include "BKE_attribute_access.hh" #include "BKE_geometry_set.hh" @@ -105,105 +107,105 @@ bool GeometryComponent::is_empty() const /** \name Geometry Set * \{ */ -/* This method can only be used when the geometry set is mutable. It returns a mutable geometry - * component of the given type. - */ +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; + GeometryComponent &GeometrySet::get_component_for_write(GeometryComponentType component_type) { - return components_.add_or_modify( - component_type, - [&](GeometryComponentPtr *value_ptr) -> GeometryComponent & { - /* If the component did not exist before, create a new one. */ - new (value_ptr) GeometryComponentPtr(GeometryComponent::create(component_type)); - return **value_ptr; - }, - [&](GeometryComponentPtr *value_ptr) -> GeometryComponent & { - GeometryComponentPtr &value = *value_ptr; - if (value->is_mutable()) { - /* If the referenced component is already mutable, return it directly. */ - return *value; - } - /* If the referenced component is shared, make a copy. The copy is not shared and is - * therefore mutable. */ - GeometryComponent *copied_component = value->copy(); - value = GeometryComponentPtr{copied_component}; - return *copied_component; - }); + GeometryComponentPtr &component_ptr = components_[component_type]; + if (!component_ptr) { + /* If the component did not exist before, create a new one. */ + component_ptr = GeometryComponent::create(component_type); + return *component_ptr; + } + if (component_ptr->is_mutable()) { + /* If the referenced component is already mutable, return it directly. */ + return *component_ptr; + } + /* If the referenced component is shared, make a copy. The copy is not shared and is + * therefore mutable. */ + component_ptr = component_ptr->copy(); + return *component_ptr; } -/* 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 +GeometryComponent *GeometrySet::get_component_ptr(GeometryComponentType type) { - const GeometryComponentPtr *component = components_.lookup_ptr(component_type); - if (component != nullptr) { - return component->get(); + if (this->has(type)) { + return &this->get_component_for_write(type); } return nullptr; } +const GeometryComponent *GeometrySet::get_component_for_read( + GeometryComponentType component_type) const +{ + return components_[component_type].get(); +} + bool GeometrySet::has(const GeometryComponentType component_type) const { - return components_.contains(component_type); + return components_[component_type].has_value(); } void GeometrySet::remove(const GeometryComponentType component_type) { - components_.remove(component_type); + components_[component_type].reset(); } -/** - * 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); + for (GeometryComponentPtr &component_ptr : components_) { + if (component_ptr) { + if (!component_types.contains(component_ptr->type())) { + component_ptr.reset(); + } } } } void GeometrySet::add(const GeometryComponent &component) { - BLI_assert(!components_.contains(component.type())); + BLI_assert(!components_[component.type()]); component.user_add(); - GeometryComponentPtr component_ptr{const_cast<GeometryComponent *>(&component)}; - components_.add_new(component.type(), std::move(component_ptr)); + components_[component.type()] = const_cast<GeometryComponent *>(&component); } -/** - * Get all geometry components in this geometry set for read-only access. - */ Vector<const GeometryComponent *> GeometrySet::get_components_for_read() const { Vector<const GeometryComponent *> components; - for (const GeometryComponentPtr &ptr : components_.values()) { - components.append(ptr.get()); + for (const GeometryComponentPtr &component_ptr : components_) { + if (component_ptr) { + components.append(component_ptr.get()); + } } return components; } -void GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_max) const +bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_max) const { + bool have_minmax = false; const PointCloud *pointcloud = this->get_pointcloud_for_read(); if (pointcloud != nullptr) { - BKE_pointcloud_minmax(pointcloud, *r_min, *r_max); + have_minmax |= BKE_pointcloud_minmax(pointcloud, *r_min, *r_max); } const Mesh *mesh = this->get_mesh_for_read(); if (mesh != nullptr) { - BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max); + have_minmax |= BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max); } const Volume *volume = this->get_volume_for_read(); if (volume != nullptr) { - BKE_volume_min_max(volume, *r_min, *r_max); + have_minmax |= BKE_volume_min_max(volume, *r_min, *r_max); } const CurveEval *curve = this->get_curve_for_read(); if (curve != nullptr) { /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */ - curve->bounds_min_max(*r_min, *r_max, true); + have_minmax |= curve->bounds_min_max(*r_min, *r_max, true); } + return have_minmax; } std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) @@ -213,203 +215,220 @@ std::ostream &operator<<(std::ostream &stream, const GeometrySet &geometry_set) return stream; } -/* Remove all geometry components from the geometry set. */ void GeometrySet::clear() { - components_.clear(); + for (GeometryComponentPtr &component_ptr : components_) { + component_ptr.reset(); + } } -/* Make sure that the geometry can be cached. This does not ensure ownership of object/collection - * instances. */ void GeometrySet::ensure_owns_direct_data() { - for (GeometryComponentType type : components_.keys()) { - const GeometryComponent *component = this->get_component_for_read(type); - if (!component->owns_direct_data()) { - GeometryComponent &component_for_write = this->get_component_for_write(type); - component_for_write.ensure_owns_direct_data(); + for (GeometryComponentPtr &component_ptr : components_) { + if (!component_ptr) { + continue; + } + if (component_ptr->owns_direct_data()) { + continue; } + GeometryComponent &component_for_write = this->get_component_for_write(component_ptr->type()); + component_for_write.ensure_owns_direct_data(); } } bool GeometrySet::owns_direct_data() const { - for (const GeometryComponentPtr &component : components_.values()) { - if (!component->owns_direct_data()) { - return false; + for (const GeometryComponentPtr &component_ptr : components_) { + if (component_ptr) { + if (!component_ptr->owns_direct_data()) { + return false; + } } } return true; } -/* Returns a read-only mesh or null. */ const Mesh *GeometrySet::get_mesh_for_read() const { const MeshComponent *component = this->get_component_for_read<MeshComponent>(); return (component == nullptr) ? nullptr : component->get_for_read(); } -/* Returns true when the geometry set has a mesh component that has a mesh. */ bool GeometrySet::has_mesh() const { const MeshComponent *component = this->get_component_for_read<MeshComponent>(); return component != nullptr && component->has_mesh(); } -/* Returns a read-only point cloud of null. */ const PointCloud *GeometrySet::get_pointcloud_for_read() const { const PointCloudComponent *component = this->get_component_for_read<PointCloudComponent>(); return (component == nullptr) ? nullptr : component->get_for_read(); } -/* Returns a read-only volume or null. */ const Volume *GeometrySet::get_volume_for_read() const { const VolumeComponent *component = this->get_component_for_read<VolumeComponent>(); return (component == nullptr) ? nullptr : component->get_for_read(); } -/* Returns a read-only curve or null. */ const CurveEval *GeometrySet::get_curve_for_read() const { const CurveComponent *component = this->get_component_for_read<CurveComponent>(); return (component == nullptr) ? nullptr : component->get_for_read(); } -/* Returns true when the geometry set has a point cloud component that has a point cloud. */ bool GeometrySet::has_pointcloud() const { const PointCloudComponent *component = this->get_component_for_read<PointCloudComponent>(); return component != nullptr && component->has_pointcloud(); } -/* Returns true when the geometry set has an instances component that has at least one instance. */ bool GeometrySet::has_instances() const { const InstancesComponent *component = this->get_component_for_read<InstancesComponent>(); return component != nullptr && component->instances_amount() >= 1; } -/* Returns true when the geometry set has a volume component that has a volume. */ bool GeometrySet::has_volume() const { const VolumeComponent *component = this->get_component_for_read<VolumeComponent>(); return component != nullptr && component->has_volume(); } -/* Returns true when the geometry set has a curve component that has a curve. */ bool GeometrySet::has_curve() const { const CurveComponent *component = this->get_component_for_read<CurveComponent>(); 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; + for (const GeometryComponentPtr &component_ptr : components_) { + if (component_ptr) { + if (component_ptr->type() != GEO_COMPONENT_TYPE_INSTANCES) { + return true; + } + } } - /* Check if the only component is an #InstancesComponent. */ - return this->get_component_for_read<InstancesComponent>() == nullptr; + return false; } -/* 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() || + return !(this->has_mesh() || this->has_curve() || this->has_pointcloud() || this->has_volume() || 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; } -/* Create a new geometry set that only contains the given point cloud. */ 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; } -/* Create a new geometry set that only contains the given curve. */ 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) { + if (mesh == nullptr) { + this->remove<MeshComponent>(); + return; + } + if (mesh == this->get_mesh_for_read()) { + return; + } + this->remove<MeshComponent>(); 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) { + if (curve == nullptr) { + this->remove<CurveComponent>(); + return; + } + if (curve == this->get_curve_for_read()) { + return; + } + this->remove<CurveComponent>(); 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>(); + return; + } + if (pointcloud == this->get_pointcloud_for_read()) { + return; + } + this->remove<PointCloudComponent>(); + 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>(); + return; + } + if (volume == this->get_volume_for_read()) { + return; + } + this->remove<VolumeComponent>(); + 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, @@ -461,19 +480,23 @@ void GeometrySet::gather_attributes_for_propagation( return; } } - if (attribute_id.is_anonymous()) { - if (!BKE_anonymous_attribute_id_has_strong_references(&attribute_id.anonymous_id())) { - /* Don't propagate anonymous attributes that are not used anymore. */ - return; - } + + if (!attribute_id.should_be_kept()) { + return; } + + AttributeDomain domain = meta_data.domain; + if (dst_component_type != GEO_COMPONENT_TYPE_INSTANCES && domain == ATTR_DOMAIN_INSTANCE) { + domain = ATTR_DOMAIN_POINT; + } + auto add_info = [&](AttributeKind *attribute_kind) { - attribute_kind->domain = meta_data.domain; + attribute_kind->domain = 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->domain, domain}); attribute_kind->data_type = bke::attribute_data_type_highest_complexity( {attribute_kind->data_type, meta_data.data_type}); }; @@ -482,6 +505,40 @@ void GeometrySet::gather_attributes_for_propagation( 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) { @@ -502,10 +559,6 @@ static void gather_mutable_geometry_sets(GeometrySet &geometry_set, } } -/** - * 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; @@ -517,6 +570,48 @@ void GeometrySet::modify_geometry_sets(ForeachSubGeometryCallback callback) /** \} */ /* -------------------------------------------------------------------- */ +/** \name Mesh and Curve Normals Field Input + * \{ */ + +namespace blender::bke { + +GVArray NormalFieldInput::get_varray_for_context(const GeometryComponent &component, + const AttributeDomain domain, + IndexMask mask) const +{ + if (component.type() == GEO_COMPONENT_TYPE_MESH) { + const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component); + if (const Mesh *mesh = mesh_component.get_for_read()) { + return mesh_normals_varray(mesh_component, *mesh, mask, domain); + } + } + else if (component.type() == GEO_COMPONENT_TYPE_CURVE) { + const CurveComponent &curve_component = static_cast<const CurveComponent &>(component); + return curve_normals_varray(curve_component, domain); + } + return {}; +} + +std::string NormalFieldInput::socket_inspection_name() const +{ + return TIP_("Normal"); +} + +uint64_t NormalFieldInput::hash() const +{ + return 213980475983; +} + +bool NormalFieldInput::is_equal_to(const fn::FieldNode &other) const +{ + return dynamic_cast<const NormalFieldInput *>(&other) != nullptr; +} + +} // namespace blender::bke + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name C API * \{ */ @@ -531,24 +626,32 @@ bool BKE_object_has_geometry_set_instances(const Object *ob) if (geometry_set == nullptr) { return false; } - if (geometry_set->has_instances()) { - return true; - } - const bool has_mesh = geometry_set->has_mesh(); - const bool has_pointcloud = geometry_set->has_pointcloud(); - const bool has_volume = geometry_set->has_volume(); - const bool has_curve = geometry_set->has_curve(); - if (ob->type == OB_MESH) { - return has_pointcloud || has_volume || has_curve; - } - if (ob->type == OB_POINTCLOUD) { - return has_mesh || has_volume || has_curve; - } - if (ob->type == OB_VOLUME) { - return has_mesh || has_pointcloud || has_curve; - } - if (ELEM(ob->type, OB_CURVE, OB_FONT)) { - return has_mesh || has_pointcloud || has_volume; + 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 ad13342ad9e..42d2211c360 100644 --- a/source/blender/blenkernel/intern/geometry_set_instances.cc +++ b/source/blender/blenkernel/intern/geometry_set_instances.cc @@ -53,10 +53,7 @@ static void add_final_mesh_as_geometry_component(const Object &object, GeometryS } } -/** - * \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; @@ -72,25 +69,33 @@ static GeometrySet object_get_geometry_set_for_read(const Object &object) } /* Otherwise, construct a new geometry set with the component based on the object type. */ - GeometrySet geometry_set; if (object.type == OB_MESH) { + GeometrySet geometry_set; add_final_mesh_as_geometry_component(object, geometry_set); + return geometry_set; + } + if (object.type == OB_EMPTY && object.instance_collection != nullptr) { + GeometrySet geometry_set; + Collection &collection = *object.instance_collection; + InstancesComponent &instances = geometry_set.get_component_for_write<InstancesComponent>(); + const int handle = instances.add_reference(collection); + instances.add_instance(handle, float4x4::identity()); + return geometry_set; } - /* TODO: Cover the case of point-clouds without modifiers-- they may not be covered by the + /* TODO: Cover the case of point clouds without modifiers-- they may not be covered by the * #geometry_set_eval case above. */ /* TODO: Add volume support. */ /* Return by value since there is not always an existing geometry set owned elsewhere to use. */ - return geometry_set; + return {}; } static void geometry_set_collect_recursive_collection_instance( const Collection &collection, const float4x4 &transform, Vector<GeometryInstanceGroup> &r_sets) { - float4x4 offset_matrix; - unit_m4(offset_matrix.values); + float4x4 offset_matrix = float4x4::identity(); sub_v3_v3(offset_matrix.values[3], collection.instance_offset); const float4x4 instance_transform = transform * offset_matrix; geometry_set_collect_recursive_collection(collection, instance_transform, r_sets); @@ -100,15 +105,8 @@ 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) { - const Collection *collection_instance = object.instance_collection; - if (collection_instance != nullptr) { - geometry_set_collect_recursive_collection_instance(*collection_instance, transform, r_sets); - } - } } static void geometry_set_collect_recursive_collection(const Collection &collection, @@ -170,23 +168,10 @@ static void geometry_set_collect_recursive(const GeometrySet &geometry_set, } } -/** - * Return flattened vector of the geometry component's recursive instances. I.e. all collection - * instances and object instances will be expanded into the instances of their geometry components. - * Even the instances in those geometry components' will be included. - * - * \note For convenience (to avoid duplication in the caller), the returned vector also contains - * the argument geometry set. - * - * \note This doesn't extract instances from the "dupli" system for non-geometry-nodes instances. - */ void geometry_set_gather_instances(const GeometrySet &geometry_set, Vector<GeometryInstanceGroup> &r_instance_groups) { - float4x4 unit_transform; - unit_m4(unit_transform.values); - - geometry_set_collect_recursive(geometry_set, unit_transform, r_instance_groups); + geometry_set_collect_recursive(geometry_set, float4x4::identity(), r_instance_groups); } void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups, @@ -224,400 +209,6 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se } } -static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGroup> set_groups, - const bool convert_points_to_vertices) -{ - int totverts = 0; - int totloops = 0; - int totedges = 0; - int totpolys = 0; - int64_t cd_dirty_vert = 0; - int64_t cd_dirty_poly = 0; - int64_t cd_dirty_edge = 0; - int64_t cd_dirty_loop = 0; - VectorSet<Material *> materials; - - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - const int tot_transforms = set_group.transforms.size(); - if (set.has_mesh()) { - const Mesh &mesh = *set.get_mesh_for_read(); - totverts += mesh.totvert * tot_transforms; - totloops += mesh.totloop * tot_transforms; - totedges += mesh.totedge * tot_transforms; - totpolys += mesh.totpoly * tot_transforms; - cd_dirty_vert |= mesh.runtime.cd_dirty_vert; - cd_dirty_poly |= mesh.runtime.cd_dirty_poly; - cd_dirty_edge |= mesh.runtime.cd_dirty_edge; - cd_dirty_loop |= mesh.runtime.cd_dirty_loop; - for (const int slot_index : IndexRange(mesh.totcol)) { - Material *material = mesh.mat[slot_index]; - 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. */ - if ((totverts + totloops + totedges + totpolys) == 0) { - return nullptr; - } - - Mesh *new_mesh = BKE_mesh_new_nomain(totverts, totedges, 0, totloops, totpolys); - /* Copy settings from the first input geometry set with a mesh. */ - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - if (set.has_mesh()) { - const Mesh &mesh = *set.get_mesh_for_read(); - BKE_mesh_copy_parameters_for_eval(new_mesh, &mesh); - break; - } - } - for (const int i : IndexRange(materials.size())) { - Material *material = materials[i]; - BKE_id_material_eval_assign(&new_mesh->id, i + 1, material); - } - new_mesh->runtime.cd_dirty_vert = cd_dirty_vert; - new_mesh->runtime.cd_dirty_poly = cd_dirty_poly; - new_mesh->runtime.cd_dirty_edge = cd_dirty_edge; - new_mesh->runtime.cd_dirty_loop = cd_dirty_loop; - - int vert_offset = 0; - int loop_offset = 0; - int edge_offset = 0; - int poly_offset = 0; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - if (set.has_mesh()) { - const Mesh &mesh = *set.get_mesh_for_read(); - - Array<int> material_index_map(mesh.totcol); - for (const int i : IndexRange(mesh.totcol)) { - Material *material = mesh.mat[i]; - const int new_material_index = materials.index_of(material); - material_index_map[i] = new_material_index; - } - - for (const float4x4 &transform : set_group.transforms) { - for (const int i : IndexRange(mesh.totvert)) { - const MVert &old_vert = mesh.mvert[i]; - MVert &new_vert = new_mesh->mvert[vert_offset + i]; - - new_vert = old_vert; - - const float3 new_position = transform * float3(old_vert.co); - copy_v3_v3(new_vert.co, new_position); - } - for (const int i : IndexRange(mesh.totedge)) { - const MEdge &old_edge = mesh.medge[i]; - MEdge &new_edge = new_mesh->medge[edge_offset + i]; - new_edge = old_edge; - new_edge.v1 += vert_offset; - new_edge.v2 += vert_offset; - } - for (const int i : IndexRange(mesh.totloop)) { - const MLoop &old_loop = mesh.mloop[i]; - MLoop &new_loop = new_mesh->mloop[loop_offset + i]; - new_loop = old_loop; - new_loop.v += vert_offset; - new_loop.e += edge_offset; - } - for (const int i : IndexRange(mesh.totpoly)) { - const MPoly &old_poly = mesh.mpoly[i]; - MPoly &new_poly = new_mesh->mpoly[poly_offset + i]; - new_poly = old_poly; - new_poly.loopstart += loop_offset; - if (old_poly.mat_nr >= 0 && old_poly.mat_nr < mesh.totcol) { - new_poly.mat_nr = material_index_map[new_poly.mat_nr]; - } - else { - /* The material index was invalid before. */ - new_poly.mat_nr = 0; - } - } - - vert_offset += mesh.totvert; - loop_offset += mesh.totloop; - edge_offset += mesh.totedge; - 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<AttributeIDRef, AttributeKind> &attribute_info, - GeometryComponent &result) -{ - 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); - BLI_assert(cpp_type != nullptr); - - result.attribute_try_create( - entry.key, domain_output, data_type_output, AttributeInitDefault()); - 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; - } - - fn::GVMutableArray_GSpan dst_span{*write_attribute.varray}; - - int offset = 0; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - for (const GeometryComponentType component_type : component_types) { - if (set.has(component_type)) { - const GeometryComponent &component = *set.get_component_for_read(component_type); - const int domain_size = component.attribute_domain_size(domain_output); - if (domain_size == 0) { - continue; /* Domain size is 0, so no need to increment the offset. */ - } - GVArrayPtr source_attribute = component.attribute_try_get_for_read( - attribute_id, domain_output, data_type_output); - - if (source_attribute) { - fn::GVArray_GSpan src_span{*source_attribute}; - const void *src_buffer = src_span.data(); - for (const int UNUSED(i) : set_group.transforms.index_range()) { - void *dst_buffer = dst_span[offset]; - cpp_type->copy_assign_n(src_buffer, dst_buffer, domain_size); - offset += domain_size; - } - } - else { - offset += domain_size * set_group.transforms.size(); - } - } - } - } - - dst_span.save(); - } -} - -static PointCloud *join_pointcloud_position_attribute(Span<GeometryInstanceGroup> set_groups) -{ - /* Count the total number of points. */ - int totpoint = 0; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - if (set.has<PointCloudComponent>()) { - const PointCloudComponent &component = *set.get_component_for_read<PointCloudComponent>(); - totpoint += component.attribute_domain_size(ATTR_DOMAIN_POINT); - } - } - if (totpoint == 0) { - return nullptr; - } - - 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; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - const PointCloud *pointcloud = set.get_pointcloud_for_read(); - if (pointcloud == nullptr) { - continue; - } - for (const float4x4 &transform : set_group.transforms) { - for (const int i : IndexRange(pointcloud->totpoint)) { - new_positions[offset + i] = transform * float3(pointcloud->co[i]); - } - offset += pointcloud->totpoint; - } - } - - return new_pointcloud; -} - -static CurveEval *join_curve_splines_and_builtin_attributes(Span<GeometryInstanceGroup> set_groups) -{ - Vector<SplinePtr> new_splines; - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - if (!set.has_curve()) { - continue; - } - - const CurveEval &source_curve = *set.get_curve_for_read(); - for (const SplinePtr &source_spline : source_curve.splines()) { - for (const float4x4 &transform : set_group.transforms) { - SplinePtr new_spline = source_spline->copy_without_attributes(); - new_spline->transform(transform); - new_splines.append(std::move(new_spline)); - } - } - } - if (new_splines.is_empty()) { - return nullptr; - } - - CurveEval *new_curve = new CurveEval(); - for (SplinePtr &new_spline : new_splines) { - new_curve->add_spline(std::move(new_spline)); - } - - new_curve->attributes.reallocate(new_curve->splines().size()); - return new_curve; -} - -static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups, - bool convert_points_to_vertices, - GeometrySet &result) -{ - Mesh *new_mesh = join_mesh_topology_and_builtin_attributes(set_groups, - convert_points_to_vertices); - if (new_mesh == nullptr) { - return; - } - - 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<AttributeIDRef, AttributeKind> attributes; - geometry_set_gather_instances_attribute_info( - set_groups, - component_types, - {"position", "material_index", "normal", "shade_smooth", "crease"}, - attributes); - join_attributes( - set_groups, component_types, attributes, static_cast<GeometryComponent &>(dst_component)); -} - -static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_groups, - GeometrySet &result) -{ - PointCloud *new_pointcloud = join_pointcloud_position_attribute(set_groups); - if (new_pointcloud == nullptr) { - return; - } - - PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>(); - dst_component.replace(new_pointcloud); - - Map<AttributeIDRef, AttributeKind> attributes; - geometry_set_gather_instances_attribute_info( - set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes); - join_attributes(set_groups, - {GEO_COMPONENT_TYPE_POINT_CLOUD}, - attributes, - static_cast<GeometryComponent &>(dst_component)); -} - -static void join_instance_groups_volume(Span<GeometryInstanceGroup> set_groups, - GeometrySet &result) -{ - /* Not yet supported; for now only return the first volume. Joining volume grids with the same - * name requires resampling of at least one of the grids. The cell size of the resulting volume - * has to be determined somehow. */ - for (const GeometryInstanceGroup &set_group : set_groups) { - const GeometrySet &set = set_group.geometry_set; - if (set.has<VolumeComponent>()) { - result.add(*set.get_component_for_read<VolumeComponent>()); - return; - } - } -} - -static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, GeometrySet &result) -{ - CurveEval *curve = join_curve_splines_and_builtin_attributes(set_groups); - if (curve == nullptr) { - return; - } - - CurveComponent &dst_component = result.get_component_for_write<CurveComponent>(); - dst_component.replace(curve); - - Map<AttributeIDRef, AttributeKind> attributes; - geometry_set_gather_instances_attribute_info( - set_groups, - {GEO_COMPONENT_TYPE_CURVE}, - {"position", "radius", "tilt", "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; -} - -GeometrySet geometry_set_realize_instances(const GeometrySet &geometry_set) -{ - if (!geometry_set.has_instances()) { - return geometry_set; - } - - GeometrySet new_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_pointcloud(set_groups, new_geometry_set); - join_instance_groups_volume(set_groups, new_geometry_set); - join_instance_groups_curve(set_groups, new_geometry_set); - - return new_geometry_set; -} - } // namespace blender::bke void InstancesComponent::foreach_referenced_geometry( @@ -628,14 +219,14 @@ void InstancesComponent::foreach_referenced_geometry( switch (reference.type()) { case InstanceReference::Type::Object: { const Object &object = reference.object(); - const GeometrySet object_geometry_set = object_get_geometry_set_for_read(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_geometry_set_for_read(*object); + const GeometrySet object_geometry_set = object_get_evaluated_geometry_set(*object); callback(object_geometry_set); } FOREACH_COLLECTION_OBJECT_RECURSIVE_END; @@ -653,11 +244,6 @@ void InstancesComponent::foreach_referenced_geometry( } } -/** - * 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; @@ -676,7 +262,7 @@ void InstancesComponent::ensure_geometry_instances() /* 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_geometry_set_for_read(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>(); diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index ed84694a919..13338f33bd6 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -139,11 +139,11 @@ 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); } } @@ -320,6 +320,7 @@ IDTypeInfo IDType_ID_GD = { .name_plural = "grease_pencils", .translation_context = BLT_I18NCONTEXT_ID_GPENCIL, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = NULL, .copy_data = greasepencil_copy_data, @@ -327,6 +328,7 @@ IDTypeInfo IDType_ID_GD = { .make_local = NULL, .foreach_id = greasepencil_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = greasepencil_blend_write, @@ -363,7 +365,6 @@ void BKE_gpencil_batch_cache_free(bGPdata *gpd) /* ************************************************** */ /* Memory Management */ -/* clean vertex groups weights */ void BKE_gpencil_free_point_weights(MDeformVert *dvert) { if (dvert == NULL) { @@ -402,7 +403,6 @@ void BKE_gpencil_free_stroke_editcurve(bGPDstroke *gps) gps->editcurve = NULL; } -/* free stroke, doesn't unlink from any listbase */ void BKE_gpencil_free_stroke(bGPDstroke *gps) { if (gps == NULL) { @@ -426,7 +426,6 @@ void BKE_gpencil_free_stroke(bGPDstroke *gps) MEM_freeN(gps); } -/* Free strokes belonging to a gp-frame */ bool BKE_gpencil_free_strokes(bGPDframe *gpf) { bool changed = (BLI_listbase_is_empty(&gpf->strokes) == false); @@ -440,7 +439,6 @@ bool BKE_gpencil_free_strokes(bGPDframe *gpf) return changed; } -/* Free all of a gp-layer's frames */ void BKE_gpencil_free_frames(bGPDlayer *gpl) { bGPDframe *gpf_next; @@ -470,7 +468,6 @@ void BKE_gpencil_free_layer_masks(bGPDlayer *gpl) BLI_freelinkN(&gpl->mask_layers, mask); } } -/* Free all of the gp-layers for a viewport (list should be &gpd->layers or so) */ void BKE_gpencil_free_layers(ListBase *list) { bGPDlayer *gpl_next; @@ -494,7 +491,6 @@ 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_data(bGPdata *gpd, bool free_all) { /* free layers */ @@ -512,10 +508,6 @@ void BKE_gpencil_free_data(bGPdata *gpd, bool free_all) } } -/** - * Delete grease pencil evaluated data - * \param gpd_eval: Grease pencil data-block - */ void BKE_gpencil_eval_delete(bGPdata *gpd_eval) { BKE_gpencil_free_data(gpd_eval, true); @@ -524,11 +516,6 @@ void BKE_gpencil_eval_delete(bGPdata *gpd_eval) MEM_freeN(gpd_eval); } -/** - * Tag data-block for depsgraph update. - * Wrapper to avoid include Depsgraph tag functions in other modules. - * \param gpd: Grease pencil data-block. - */ void BKE_gpencil_tag(bGPdata *gpd) { DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); @@ -537,12 +524,6 @@ void BKE_gpencil_tag(bGPdata *gpd) /* ************************************************** */ /* Container Creation */ -/** - * Add a new gp-frame to the given layer. - * \param gpl: Grease pencil layer - * \param cframe: Frame number - * \return Pointer to new frame - */ bGPDframe *BKE_gpencil_frame_addnew(bGPDlayer *gpl, int cframe) { bGPDframe *gpf = NULL, *gf = NULL; @@ -596,12 +577,6 @@ bGPDframe *BKE_gpencil_frame_addnew(bGPDlayer *gpl, int cframe) return gpf; } -/** - * Add a copy of the active gp-frame to the given layer. - * \param gpl: Grease pencil layer - * \param cframe: Frame number - * \return Pointer to new frame - */ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe) { bGPDframe *new_frame; @@ -656,14 +631,6 @@ bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe) return new_frame; } -/** - * Add a new gp-layer and make it the active layer. - * \param gpd: Grease pencil data-block - * \param name: Name of the layer - * \param setactive: Set as active - * \param add_to_header: Used to force the layer added at header - * \return Pointer to new layer - */ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, const bool setactive, @@ -748,12 +715,6 @@ bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, return gpl; } -/** - * Add a new grease pencil data-block. - * \param bmain: Main pointer - * \param name: Name of the datablock - * \return Pointer to new data-block - */ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[]) { bGPdata *gpd; @@ -805,13 +766,6 @@ bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[]) /* Primitive Creation */ /* Utilities for easier bulk-creation of geometry */ -/** - * Create a new stroke, with pre-allocated data buffers. - * \param mat_idx: Index of the material - * \param totpoints: Total points - * \param thickness: Stroke thickness - * \return Pointer to new stroke - */ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness) { /* allocate memory for a new stroke */ @@ -848,15 +802,6 @@ bGPDstroke *BKE_gpencil_stroke_new(int mat_idx, int totpoints, short thickness) return gps; } -/** - * Create a new stroke and add to frame. - * \param gpf: Grease pencil frame - * \param mat_idx: Material index - * \param totpoints: Total points - * \param thickness: Stroke thickness - * \param insert_at_head: Add to the head of the strokes list - * \return Pointer to new stroke - */ bGPDstroke *BKE_gpencil_stroke_add( bGPDframe *gpf, int mat_idx, int totpoints, short thickness, const bool insert_at_head) { @@ -875,16 +820,6 @@ bGPDstroke *BKE_gpencil_stroke_add( return gps; } -/** - * Add a stroke and copy the temporary drawing color value - * from one of the existing stroke. - * \param gpf: Grease pencil frame - * \param existing: Stroke with the style to copy - * \param mat_idx: Material index - * \param totpoints: Total points - * \param thickness: Stroke thickness - * \return Pointer to new stroke - */ bGPDstroke *BKE_gpencil_stroke_add_existing_style( bGPDframe *gpf, bGPDstroke *existing, int mat_idx, int totpoints, short thickness) { @@ -909,11 +844,6 @@ bGPDcurve *BKE_gpencil_stroke_editcurve_new(const int tot_curve_points) /* ************************************************** */ /* Data Duplication */ -/** - * Make a copy of a given gpencil weights. - * \param gps_src: Source grease pencil stroke - * \param gps_dst: Destination grease pencil stroke - */ void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_dst) { if (gps_src == NULL) { @@ -924,7 +854,6 @@ void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_d BKE_defvert_array_copy(gps_dst->dvert, gps_src->dvert, gps_src->totpoints); } -/* Make a copy of a given gpencil stroke editcurve */ bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) { bGPDcurve *gpc_dst = MEM_dupallocN(gpc_src); @@ -936,13 +865,6 @@ bGPDcurve *BKE_gpencil_stroke_curve_duplicate(bGPDcurve *gpc_src) return gpc_dst; } -/** - * Make a copy of a given grease-pencil stroke. - * \param gps_src: Source grease pencil strokes. - * \param dup_points: Duplicate points data. - * \param dup_curve: Duplicate curve data. - * \return Pointer to new stroke. - */ bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, const bool dup_points, const bool dup_curve) @@ -980,11 +902,6 @@ bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src, return gps_dst; } -/** - * Make a copy of a given gpencil frame. - * \param gpf_src: Source grease pencil frame - * \return Pointer to new frame - */ bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src, const bool dup_strokes) { bGPDstroke *gps_dst = NULL; @@ -1013,11 +930,6 @@ bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src, const bool dup_ return gpf_dst; } -/** - * Make a copy of strokes between gpencil frames. - * \param gpf_src: Source grease pencil frame - * \param gpf_dst: Destination grease pencil frame - */ void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_dst) { bGPDstroke *gps_dst = NULL; @@ -1035,16 +947,10 @@ void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_ds } } -/** - * Make a copy of a given gpencil layer. - * \param gpl_src: Source grease pencil layer - * \return Pointer to new layer - */ 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; @@ -1063,7 +969,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); @@ -1079,9 +985,6 @@ bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src, return gpl_dst; } -/** - * Make a copy of a given gpencil layer settings. - */ void BKE_gpencil_layer_copy_settings(const bGPDlayer *gpl_src, bGPDlayer *gpl_dst) { gpl_dst->line_change = gpl_src->line_change; @@ -1103,11 +1006,6 @@ void BKE_gpencil_layer_copy_settings(const bGPDlayer *gpl_src, bGPDlayer *gpl_ds gpl_dst->flag = gpl_src->flag; } -/** - * Make a copy of a given gpencil data-block. - * - * XXX: Should this be deprecated? - */ bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool internal_copy) { bGPdata *gpd_dst; @@ -1140,10 +1038,6 @@ bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool in /* ************************************************** */ /* GP Stroke API */ -/** - * Ensure selection status of stroke is in sync with its points. - * \param gps: Grease pencil stroke - */ void BKE_gpencil_stroke_sync_selection(bGPdata *gpd, bGPDstroke *gps) { bGPDspoint *pt; @@ -1207,14 +1101,12 @@ void BKE_gpencil_curve_sync_selection(bGPdata *gpd, bGPDstroke *gps) } } -/* Assign unique stroke ID for selection. */ void BKE_gpencil_stroke_select_index_set(bGPdata *gpd, bGPDstroke *gps) { gpd->select_last_index++; gps->select_index = gpd->select_last_index; } -/* Reset unique stroke ID for selection. */ void BKE_gpencil_stroke_select_index_reset(bGPDstroke *gps) { gps->select_index = 0; @@ -1223,11 +1115,6 @@ void BKE_gpencil_stroke_select_index_reset(bGPDstroke *gps) /* ************************************************** */ /* GP Frame API */ -/** - * Delete the last stroke of the given frame. - * \param gpl: Grease pencil layer - * \param gpf: Grease pencil frame - */ void BKE_gpencil_frame_delete_laststroke(bGPDlayer *gpl, bGPDframe *gpf) { bGPDstroke *gps = (gpf) ? gpf->strokes.last : NULL; @@ -1259,11 +1146,6 @@ void BKE_gpencil_frame_delete_laststroke(bGPDlayer *gpl, bGPDframe *gpf) /* ************************************************** */ /* GP Layer API */ -/** - * Check if the given layer is able to be edited or not. - * \param gpl: Grease pencil layer - * \return True if layer is editable - */ bool BKE_gpencil_layer_is_editable(const bGPDlayer *gpl) { /* Sanity check */ @@ -1280,12 +1162,6 @@ bool BKE_gpencil_layer_is_editable(const bGPDlayer *gpl) return false; } -/** - * Look up the gp-frame on the requested frame number, but don't add a new one. - * \param gpl: Grease pencil layer - * \param cframe: Frame number - * \return Pointer to frame - */ bGPDframe *BKE_gpencil_layer_frame_find(bGPDlayer *gpl, int cframe) { bGPDframe *gpf; @@ -1302,16 +1178,6 @@ bGPDframe *BKE_gpencil_layer_frame_find(bGPDlayer *gpl, int cframe) return NULL; } -/** - * Get the appropriate gp-frame from a given layer - * - this sets the layer's actframe var (if allowed to) - * - extension beyond range (if first gp-frame is after all frame in interest and cannot add) - * - * \param gpl: Grease pencil layer - * \param cframe: Frame number - * \param addnew: Add option - * \return Pointer to new frame - */ bGPDframe *BKE_gpencil_layer_frame_get(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew) { bGPDframe *gpf = NULL; @@ -1466,12 +1332,6 @@ bGPDframe *BKE_gpencil_layer_frame_get(bGPDlayer *gpl, int cframe, eGP_GetFrame_ return gpl->actframe; } -/** - * Delete the given frame from a layer. - * \param gpl: Grease pencil layer - * \param gpf: Grease pencil frame - * \return True if delete was done - */ bool BKE_gpencil_layer_frame_delete(bGPDlayer *gpl, bGPDframe *gpf) { bool changed = false; @@ -1495,12 +1355,6 @@ bool BKE_gpencil_layer_frame_delete(bGPDlayer *gpl, bGPDframe *gpf) return changed; } -/** - * Get layer by name - * \param gpd: Grease pencil data-block - * \param name: Layer name - * \return Pointer to layer - */ bGPDlayer *BKE_gpencil_layer_named_get(bGPdata *gpd, const char *name) { if (name[0] == '\0') { @@ -1509,12 +1363,6 @@ bGPDlayer *BKE_gpencil_layer_named_get(bGPdata *gpd, const char *name) return BLI_findstring(&gpd->layers, name, offsetof(bGPDlayer, info)); } -/** - * Get mask layer by name. - * \param gpl: Grease pencil layer - * \param name: Mask name - * \return Pointer to mask layer - */ bGPDlayer_Mask *BKE_gpencil_layer_mask_named_get(bGPDlayer *gpl, const char *name) { if (name[0] == '\0') { @@ -1523,12 +1371,6 @@ bGPDlayer_Mask *BKE_gpencil_layer_mask_named_get(bGPDlayer *gpl, const char *nam return BLI_findstring(&gpl->mask_layers, name, offsetof(bGPDlayer_Mask, name)); } -/** - * Add grease pencil mask layer. - * \param gpl: Grease pencil layer - * \param name: Name of the mask - * \return Pointer to new mask layer - */ bGPDlayer_Mask *BKE_gpencil_layer_mask_add(bGPDlayer *gpl, const char *name) { @@ -1540,11 +1382,6 @@ bGPDlayer_Mask *BKE_gpencil_layer_mask_add(bGPDlayer *gpl, const char *name) return mask; } -/** - * Remove grease pencil mask layer. - * \param gpl: Grease pencil layer - * \param mask: Grease pencil mask layer - */ void BKE_gpencil_layer_mask_remove(bGPDlayer *gpl, bGPDlayer_Mask *mask) { BLI_freelinkN(&gpl->mask_layers, mask); @@ -1552,11 +1389,6 @@ void BKE_gpencil_layer_mask_remove(bGPDlayer *gpl, bGPDlayer_Mask *mask) CLAMP_MIN(gpl->act_mask, 0); } -/** - * Remove any reference to mask layer. - * \param gpd: Grease pencil data-block - * \param name: Name of the mask layer - */ void BKE_gpencil_layer_mask_remove_ref(bGPdata *gpd, const char *name) { bGPDlayer_Mask *mask_next; @@ -1588,11 +1420,6 @@ static int gpencil_cb_sort_masks(const void *arg1, const void *arg2) return val; } -/** - * Sort grease pencil mask layers. - * \param gpd: Grease pencil data-block - * \param gpl: Grease pencil layer - */ void BKE_gpencil_layer_mask_sort(bGPdata *gpd, bGPDlayer *gpl) { /* Update sort index. */ @@ -1608,10 +1435,6 @@ void BKE_gpencil_layer_mask_sort(bGPdata *gpd, bGPDlayer *gpl) BLI_listbase_sort(&gpl->mask_layers, gpencil_cb_sort_masks); } -/** - * Sort all grease pencil mask layer. - * \param gpd: Grease pencil data-block - */ void BKE_gpencil_layer_mask_sort_all(bGPdata *gpd) { LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { @@ -1619,9 +1442,6 @@ void BKE_gpencil_layer_mask_sort_all(bGPdata *gpd) } } -/** - * Make a copy of a given gpencil mask layers. - */ void BKE_gpencil_layer_mask_copy(const bGPDlayer *gpl_src, bGPDlayer *gpl_dst) { BLI_listbase_clear(&gpl_dst->mask_layers); @@ -1632,9 +1452,6 @@ void BKE_gpencil_layer_mask_copy(const bGPDlayer *gpl_src, bGPDlayer *gpl_dst) } } -/** - * Clean any invalid mask layer. - */ void BKE_gpencil_layer_mask_cleanup(bGPdata *gpd, bGPDlayer *gpl) { LISTBASE_FOREACH_MUTABLE (bGPDlayer_Mask *, mask, &gpl->mask_layers) { @@ -1644,9 +1461,6 @@ void BKE_gpencil_layer_mask_cleanup(bGPdata *gpd, bGPDlayer *gpl) } } -/** - * Clean any invalid mask layer for all layers. - */ void BKE_gpencil_layer_mask_cleanup_all_layers(bGPdata *gpd) { LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { @@ -1675,21 +1489,11 @@ static int gpencil_cb_cmp_frame(void *thunk, const void *a, const void *b) return 0; } -/** - * Sort grease pencil frames. - * \param gpl: Grease pencil layer - * \param r_has_duplicate_frames: Duplicated frames flag - */ void BKE_gpencil_layer_frames_sort(struct bGPDlayer *gpl, bool *r_has_duplicate_frames) { BLI_listbase_sort_r(&gpl->frames, gpencil_cb_cmp_frame, r_has_duplicate_frames); } -/** - * Get the active grease pencil layer for editing. - * \param gpd: Grease pencil data-block - * \return Pointer to layer - */ bGPDlayer *BKE_gpencil_layer_active_get(bGPdata *gpd) { /* error checking */ @@ -1733,11 +1537,6 @@ bGPDlayer *BKE_gpencil_layer_get_by_name(bGPdata *gpd, char *name, int first_if_ return NULL; } -/** - * Set active grease pencil layer. - * \param gpd: Grease pencil data-block - * \param active: Grease pencil layer to set as active - */ void BKE_gpencil_layer_active_set(bGPdata *gpd, bGPDlayer *active) { /* error checking */ @@ -1760,11 +1559,6 @@ void BKE_gpencil_layer_active_set(bGPdata *gpd, bGPDlayer *active) } } -/** - * Set locked layers for autolock mode. - * \param gpd: Grease pencil data-block - * \param unlock: Unlock flag - */ void BKE_gpencil_layer_autolock_set(bGPdata *gpd, const bool unlock) { BLI_assert(gpd != NULL); @@ -1795,11 +1589,6 @@ void BKE_gpencil_layer_autolock_set(bGPdata *gpd, const bool unlock) } } -/** - * Delete grease pencil layer. - * \param gpd: Grease pencil data-block - * \param gpl: Grease pencil layer - */ void BKE_gpencil_layer_delete(bGPdata *gpd, bGPDlayer *gpl) { /* error checking */ @@ -1822,11 +1611,6 @@ void BKE_gpencil_layer_delete(bGPdata *gpd, bGPDlayer *gpl) BLI_freelinkN(&gpd->layers, gpl); } -/** - * Get grease pencil material from brush. - * \param brush: Brush - * \return Pointer to material - */ Material *BKE_gpencil_brush_material_get(Brush *brush) { Material *ma = NULL; @@ -1839,11 +1623,6 @@ Material *BKE_gpencil_brush_material_get(Brush *brush) return ma; } -/** - * Set grease pencil brush material. - * \param brush: Brush - * \param ma: Material - */ void BKE_gpencil_brush_material_set(Brush *brush, Material *ma) { BLI_assert(brush); @@ -1859,13 +1638,6 @@ void BKE_gpencil_brush_material_set(Brush *brush, Material *ma) } } -/** - * Adds the pinned material to the object if necessary. - * \param bmain: Main pointer - * \param ob: Grease pencil object - * \param brush: Brush - * \return Pointer to material - */ Material *BKE_gpencil_object_material_ensure_from_brush(Main *bmain, Object *ob, Brush *brush) { if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) { @@ -1884,13 +1656,6 @@ Material *BKE_gpencil_object_material_ensure_from_brush(Main *bmain, Object *ob, return BKE_object_material_get(ob, ob->actcol); } -/** - * Assigns the material to object (if not already present) and returns its index (mat_nr). - * \param bmain: Main pointer - * \param ob: Grease pencil object - * \param material: Material - * \return Index of the material - */ int BKE_gpencil_object_material_ensure(Main *bmain, Object *ob, Material *material) { if (!material) { @@ -1905,14 +1670,6 @@ int BKE_gpencil_object_material_ensure(Main *bmain, Object *ob, Material *materi return index; } -/** - * Creates a new grease-pencil material and assigns it to object. - * \param bmain: Main pointer - * \param ob: Grease pencil object - * \param name: Material name - * \param r_index: value is set to zero based index of the new material if \a r_index is not NULL. - * \return Material pointer. - */ Material *BKE_gpencil_object_material_new(Main *bmain, Object *ob, const char *name, int *r_index) { Material *ma = BKE_gpencil_material_add(bmain, name); @@ -1927,12 +1684,6 @@ Material *BKE_gpencil_object_material_new(Main *bmain, Object *ob, const char *n return ma; } -/** - * Returns the material for a brush with respect to its pinned state. - * \param ob: Grease pencil object - * \param brush: Brush - * \return Material pointer - */ Material *BKE_gpencil_object_material_from_brush_get(Object *ob, Brush *brush) { if ((brush) && (brush->gpencil_settings) && @@ -1944,12 +1695,6 @@ Material *BKE_gpencil_object_material_from_brush_get(Object *ob, Brush *brush) return BKE_object_material_get(ob, ob->actcol); } -/** - * Returns the material index for a brush with respect to its pinned state. - * \param ob: Grease pencil object - * \param brush: Brush - * \return Material index. - */ int BKE_gpencil_object_material_get_index_from_brush(Object *ob, Brush *brush) { if ((brush) && (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED)) { @@ -1959,12 +1704,6 @@ int BKE_gpencil_object_material_get_index_from_brush(Object *ob, Brush *brush) return ob->actcol - 1; } -/** - * Guaranteed to return a material assigned to object. Returns never NULL. - * \param bmain: Main pointer - * \param ob: Grease pencil object - * \return Material pointer. - */ Material *BKE_gpencil_object_material_ensure_from_active_input_toolsettings(Main *bmain, Object *ob, ToolSettings *ts) @@ -1977,13 +1716,6 @@ Material *BKE_gpencil_object_material_ensure_from_active_input_toolsettings(Main return BKE_gpencil_object_material_ensure_from_active_input_brush(bmain, ob, NULL); } -/** - * Guaranteed to return a material assigned to object. Returns never NULL. - * \param bmain: Main pointer - * \param ob: Grease pencil object. - * \param brush: Brush - * \return Material pointer - */ Material *BKE_gpencil_object_material_ensure_from_active_input_brush(Main *bmain, Object *ob, Brush *brush) @@ -2001,12 +1733,6 @@ Material *BKE_gpencil_object_material_ensure_from_active_input_brush(Main *bmain return BKE_gpencil_object_material_ensure_from_active_input_material(ob); } -/** - * Guaranteed to return a material assigned to object. Returns never NULL. - * Only use this for materials unrelated to user input. - * \param ob: Grease pencil object - * \return Material pointer - */ Material *BKE_gpencil_object_material_ensure_from_active_input_material(Object *ob) { Material *ma = BKE_object_material_get(ob, ob->actcol); @@ -2017,11 +1743,6 @@ Material *BKE_gpencil_object_material_ensure_from_active_input_material(Object * return BKE_material_default_gpencil(); } -/** - * Get active color, and add all default settings if we don't find anything. - * \param ob: Grease pencil object - * \return Material pointer - */ Material *BKE_gpencil_object_material_ensure_active(Object *ob) { Material *ma = NULL; @@ -2040,11 +1761,6 @@ Material *BKE_gpencil_object_material_ensure_active(Object *ob) } /* ************************************************** */ -/** - * Check if stroke has any point selected - * \param gps: Grease pencil stroke - * \return True if selected - */ bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps) { const bGPDspoint *pt; @@ -2060,11 +1776,6 @@ bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps) /* ************************************************** */ /* GP Object - Vertex Groups */ -/** - * Remove a vertex group. - * \param ob: Grease pencil object - * \param defgroup: deform group - */ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup) { bGPdata *gpd = ob->data; @@ -2104,10 +1815,6 @@ void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup) DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); } -/** - * Ensure stroke has vertex group. - * \param gps: Grease pencil stroke - */ void BKE_gpencil_dvert_ensure(bGPDstroke *gps) { if (gps->dvert == NULL) { @@ -2117,14 +1824,6 @@ void BKE_gpencil_dvert_ensure(bGPDstroke *gps) /* ************************************************** */ -/** - * Get range of selected frames in layer. - * Always the active frame is considered as selected, so if no more selected the range - * will be equal to the current active frame. - * \param gpl: Layer. - * \param r_initframe: Number of first selected frame. - * \param r_endframe: Number of last selected frame. - */ void BKE_gpencil_frame_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_endframe) { *r_initframe = gpl->actframe->framenum; @@ -2142,14 +1841,6 @@ void BKE_gpencil_frame_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_e } } -/** - * Get Falloff factor base on frame range - * \param gpf: Frame. - * \param actnum: Number of active frame in layer. - * \param f_init: Number of first selected frame. - * \param f_end: Number of last selected frame. - * \param cur_falloff: Curve with falloff factors. - */ float BKE_gpencil_multiframe_falloff_calc( bGPDframe *gpf, int actnum, int f_init, int f_end, CurveMapping *cur_falloff) { @@ -2181,12 +1872,6 @@ float BKE_gpencil_multiframe_falloff_calc( return value; } -/** - * Reassign strokes using a material. - * \param gpd: Grease pencil data-block - * \param totcol: Total materials - * \param index: Index of the material - */ void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index) { LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { @@ -2202,12 +1887,6 @@ void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index) } } -/** - * Remove strokes using a material. - * \param gpd: Grease pencil data-block - * \param index: Index of the material - * \return True if removed - */ bool BKE_gpencil_material_index_used(bGPdata *gpd, int index) { LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { @@ -2223,12 +1902,6 @@ bool BKE_gpencil_material_index_used(bGPdata *gpd, int index) return false; } -/** - * Remap material - * \param gpd: Grease pencil data-block - * \param remap: Remap index - * \param remap_len: Remap length - */ void BKE_gpencil_material_remap(struct bGPdata *gpd, const unsigned int *remap, unsigned int remap_len) @@ -2254,15 +1927,6 @@ void BKE_gpencil_material_remap(struct bGPdata *gpd, #undef MAT_NR_REMAP } -/** - * Load a table with material conversion index for merged materials. - * \param ob: Grease pencil object. - * \param hue_threshold: Threshold for Hue. - * \param sat_threshold: Threshold for Saturation. - * \param val_threshold: Threshold for Value. - * \param r_mat_table: return material table. - * \return True if done. - */ bool BKE_gpencil_merge_materials_table_get(Object *ob, const float hue_threshold, const float sat_threshold, @@ -2382,15 +2046,6 @@ bool BKE_gpencil_merge_materials_table_get(Object *ob, return changed; } -/** - * Merge similar materials - * \param ob: Grease pencil object - * \param hue_threshold: Threshold for Hue - * \param sat_threshold: Threshold for Saturation - * \param val_threshold: Threshold for Value - * \param r_removed: Number of materials removed - * \return True if done - */ bool BKE_gpencil_merge_materials(Object *ob, const float hue_threshold, const float sat_threshold, @@ -2449,10 +2104,6 @@ bool BKE_gpencil_merge_materials(Object *ob, return changed; } -/** - * Calc grease pencil statistics functions. - * \param gpd: Grease pencil data-block - */ void BKE_gpencil_stats_update(bGPdata *gpd) { gpd->totlayer = 0; @@ -2472,12 +2123,6 @@ void BKE_gpencil_stats_update(bGPdata *gpd) } } -/** - * Get material index (0-based like mat_nr not actcol). - * \param ob: Grease pencil object - * \param ma: Material - * \return Index of the material - */ int BKE_gpencil_object_material_index_get(Object *ob, Material *ma) { short *totcol = BKE_object_material_len_p(ob); @@ -2520,11 +2165,6 @@ Material *BKE_gpencil_object_material_ensure_by_name(Main *bmain, return BKE_gpencil_object_material_new(bmain, ob, name, r_index); } -/** - * Create a default palette. - * \param bmain: Main pointer - * \param scene: Scene - */ void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene) { const char *hexcol[] = { @@ -2574,15 +2214,6 @@ void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene) BKE_paint_palette_set(&ts->gp_vertexpaint->paint, palette); } -/** - * Create grease pencil strokes from image - * \param sima: Image - * \param gpd: Grease pencil data-block - * \param gpf: Grease pencil frame - * \param size: Size - * \param mask: Mask - * \return True if done - */ bool BKE_gpencil_from_image( SpaceImage *sima, bGPdata *gpd, bGPDframe *gpf, const float size, const bool mask) { @@ -2714,6 +2345,8 @@ void BKE_gpencil_visible_stroke_iter(bGPdata *gpd, } } +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Advanced Iterator * @@ -2918,11 +2551,6 @@ void BKE_gpencil_visible_stroke_advanced_iter(ViewLayer *view_layer, } } -/** - * Update original pointers in evaluated frame. - * \param gpf_orig: Original grease-pencil frame. - * \param gpf_eval: Evaluated grease pencil frame. - */ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig, const struct bGPDframe *gpf_eval) { @@ -2951,11 +2579,6 @@ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig } } -/** - * Update pointers of eval data to original data to keep references. - * \param ob_orig: Original grease pencil object - * \param ob_eval: Evaluated grease pencil object - */ void BKE_gpencil_update_orig_pointers(const Object *ob_orig, const Object *ob_eval) { bGPdata *gpd_eval = (bGPdata *)ob_eval->data; @@ -2986,13 +2609,6 @@ void BKE_gpencil_update_orig_pointers(const Object *ob_orig, const Object *ob_ev } } -/** - * Get parent matrix, including layer parenting. - * \param depsgraph: Depsgraph - * \param obact: Grease pencil object - * \param gpl: Grease pencil layer - * \param diff_mat: Result parent matrix - */ void BKE_gpencil_layer_transform_matrix_get(const Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, @@ -3041,11 +2657,6 @@ void BKE_gpencil_layer_transform_matrix_get(const Depsgraph *depsgraph, unit_m4(diff_mat); /* not defined type */ } -/** - * Update parent matrix and local transforms. - * \param depsgraph: Depsgraph - * \param ob: Grease pencil object - */ void BKE_gpencil_update_layer_transforms(const Depsgraph *depsgraph, Object *ob) { if (ob->type != OB_GPENCIL) { @@ -3105,12 +2716,6 @@ void BKE_gpencil_update_layer_transforms(const Depsgraph *depsgraph, Object *ob) } } -/** - * Find material by name prefix. - * \param ob: Object pointer - * \param name_prefix: Prefix name of the material - * \return Index - */ int BKE_gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_prefix) { const int name_prefix_len = strlen(name_prefix); @@ -3125,7 +2730,6 @@ int BKE_gpencil_material_find_index_by_name_prefix(Object *ob, const char *name_ return -1; } -/* Create a hash with the list of selected frame number. */ void BKE_gpencil_frame_selected_hash(bGPdata *gpd, struct GHash *r_list) { const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd); diff --git a/source/blender/blenkernel/intern/gpencil_curve.c b/source/blender/blenkernel/intern/gpencil_curve.c index 3819c0699f4..d633678b873 100644 --- a/source/blender/blenkernel/intern/gpencil_curve.c +++ b/source/blender/blenkernel/intern/gpencil_curve.c @@ -477,17 +477,6 @@ static void gpencil_editstroke_deselect_all(bGPDcurve *gpc) gpc->flag &= ~GP_CURVE_SELECT; } -/** - * Convert a curve object to grease pencil stroke. - * - * \param bmain: Main thread pointer - * \param scene: Original scene. - * \param ob_gp: Grease pencil object to add strokes. - * \param ob_cu: Curve to convert. - * \param use_collections: Create layers using collection names. - * \param scale_thickness: Scale thickness factor. - * \param sample: Sample distance, zero to disable. - */ void BKE_gpencil_convert_curve(Main *bmain, Scene *scene, Object *ob_gp, @@ -639,9 +628,6 @@ static bGPDcurve *gpencil_stroke_editcurve_generate_edgecases(bGPDstroke *gps, return NULL; } -/** - * Creates a bGPDcurve by doing a cubic curve fitting on the grease pencil stroke points. - */ bGPDcurve *BKE_gpencil_stroke_editcurve_generate(bGPDstroke *gps, const float error_threshold, const float corner_angle, @@ -753,9 +739,6 @@ bGPDcurve *BKE_gpencil_stroke_editcurve_generate(bGPDstroke *gps, return editcurve; } -/** - * Updates the editcurve for a stroke. Frees the old curve if one exists and generates a new one. - */ void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstroke *gps) { if (gps == NULL || gps->totpoints < 0) { @@ -778,9 +761,6 @@ void BKE_gpencil_stroke_editcurve_update(bGPdata *gpd, bGPDlayer *gpl, bGPDstrok gps->editcurve = editcurve; } -/** - * Sync the selection from stroke to editcurve - */ void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *UNUSED(gpd), bGPDstroke *gps, bGPDcurve *gpc) @@ -807,9 +787,6 @@ void BKE_gpencil_editcurve_stroke_sync_selection(bGPdata *UNUSED(gpd), } } -/** - * Sync the selection from editcurve to stroke - */ void BKE_gpencil_stroke_editcurve_sync_selection(bGPdata *gpd, bGPDstroke *gps, bGPDcurve *gpc) { if (gpc->flag & GP_CURVE_SELECT) { @@ -1055,9 +1032,6 @@ static float *gpencil_stroke_points_from_editcurve_fixed_resolu(bGPDcurve_point return (float(*))r_points; } -/** - * Recalculate stroke points with the editcurve of the stroke. - */ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, const uint resolution, const bool adaptive) @@ -1142,9 +1116,6 @@ void BKE_gpencil_stroke_update_geometry_from_editcurve(bGPDstroke *gps, MEM_freeN(points); } -/** - * Recalculate the handles of the edit curve of a grease pencil stroke - */ void BKE_gpencil_editcurve_recalculate_handles(bGPDstroke *gps) { if (gps == NULL || gps->editcurve == NULL) { @@ -1167,7 +1138,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.cc b/source/blender/blenkernel/intern/gpencil_geom.cc index debdf44b0bb..9abdbceec61 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.cc +++ b/source/blender/blenkernel/intern/gpencil_geom.cc @@ -33,10 +33,10 @@ #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_vec_types.hh" #include "BLI_math_vector.h" #include "BLI_polyfill_2d.h" #include "BLI_span.hh" @@ -60,6 +60,7 @@ #include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" +#include "BKE_mesh.h" #include "BKE_object.h" #include "DEG_depsgraph_query.h" @@ -67,15 +68,10 @@ using blender::float3; using blender::Span; -/* GP Object - Boundbox Support */ -/** - *Get min/max coordinate bounds for single stroke. - * \param gps: Grease pencil stroke - * \param use_select: Include only selected points - * \param r_min: Result minimum coordinates - * \param r_max: Result maximum coordinates - * \return True if it was possible to calculate - */ +/* -------------------------------------------------------------------- */ +/** \name Grease Pencil Object: Bound-box Support + * \{ */ + bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps, const bool use_select, float r_min[3], @@ -104,13 +100,6 @@ bool BKE_gpencil_stroke_minmax(const bGPDstroke *gps, return changed; } -/** - * Get min/max bounds of all strokes in grease pencil data-block. - * \param gpd: Grease pencil datablock - * \param r_min: Result minimum coordinates - * \param r_max: Result maximum coordinates - * \return True if it was possible to calculate - */ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3]) { bool changed = false; @@ -134,11 +123,6 @@ bool BKE_gpencil_data_minmax(const bGPdata *gpd, float r_min[3], float r_max[3]) return changed; } -/** - * Compute center of bounding box. - * \param gpd: Grease pencil data-block - * \param r_centroid: Location of the center - */ void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3]) { float3 min; @@ -149,10 +133,6 @@ void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3]) mul_v3_v3fl(r_centroid, tot, 0.5f); } -/** - * Compute stroke bounding box. - * \param gps: Grease pencil Stroke - */ void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps) { INIT_MINMAX(gps->boundbox_min, gps->boundbox_max); @@ -166,7 +146,7 @@ void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps) static void boundbox_gpencil(Object *ob) { if (ob->runtime.bb == nullptr) { - ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "GPencil boundbox"); + ob->runtime.bb = MEM_cnew<BoundBox>("GPencil boundbox"); } BoundBox *bb = ob->runtime.bb; @@ -184,11 +164,6 @@ static void boundbox_gpencil(Object *ob) bb->flag &= ~BOUNDBOX_DIRTY; } -/** - * Get grease pencil object bounding box. - * \param ob: Grease pencil object - * \return Bounding box - */ BoundBox *BKE_gpencil_boundbox_get(Object *ob) { if (ELEM(nullptr, ob, ob->data)) { @@ -208,7 +183,7 @@ BoundBox *BKE_gpencil_boundbox_get(Object *ob) * to keep both values synchronized. */ if (!ELEM(ob_orig, nullptr, ob)) { if (ob_orig->runtime.bb == nullptr) { - ob_orig->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "GPencil boundbox"); + ob_orig->runtime.bb = MEM_cnew<BoundBox>("GPencil boundbox"); } for (int i = 0; i < 8; i++) { copy_v3_v3(ob_orig->runtime.bb->vec[i], ob->runtime.bb->vec[i]); @@ -218,7 +193,11 @@ BoundBox *BKE_gpencil_boundbox_get(Object *ob) return ob->runtime.bb; } -/* ************************************************** */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Sample + * \{ */ static int stroke_march_next_point(const bGPDstroke *gps, const int index_next_pt, @@ -386,7 +365,7 @@ static void stroke_defvert_create_nr_list(MDeformVert *dv_list, } } if (!found) { - ld = (LinkData *)MEM_callocN(sizeof(LinkData), "def_nr_item"); + ld = MEM_cnew<LinkData>("def_nr_item"); ld->data = POINTER_FROM_INT(dw->def_nr); BLI_addtail(result, ld); tw++; @@ -431,12 +410,6 @@ static void stroke_interpolate_deform_weights( } } -/** - * Resample a stroke - * \param gpd: Grease pencil data-block - * \param gps: Stroke to sample - * \param dist: Distance of one segment - */ bool BKE_gpencil_stroke_sample(bGPdata *gpd, bGPDstroke *gps, const float dist, const bool select) { bGPDspoint *pt = gps->points; @@ -594,15 +567,6 @@ static bool BKE_gpencil_stroke_extra_points(bGPDstroke *gps, return true; } -/** - * Backbone stretch similar to Freestyle. - * \param gps: Stroke to sample. - * \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, @@ -779,12 +743,12 @@ bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, return true; } -/** - * Trim stroke to needed segments - * \param gps: Target stroke - * \param index_from: the index of the first point to be used in the trimmed result - * \param index_to: the index of the last point to be used in the trimmed result - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Trim + * \{ */ + bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const int index_to) { bGPDspoint *pt = gps->points, *new_pt; @@ -837,15 +801,12 @@ bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const return true; } -/** - * Split stroke. - * \param gpd: Grease pencil data-block - * \param gpf: Grease pencil frame - * \param gps: Grease pencil original stroke - * \param before_index: Position of the point to split - * \param remaining_gps: Secondary stroke after split. - * \return True if the split was done - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Split + * \{ */ + bool BKE_gpencil_stroke_split(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, @@ -898,12 +859,12 @@ bool BKE_gpencil_stroke_split(bGPdata *gpd, return true; } -/** - * Shrink the stroke by length. - * \param gps: Stroke to shrink - * \param dist: delta length - * \param mode: 1->Start, 2->End - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Shrink + * \{ */ + bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mode) { #define START 1 @@ -976,13 +937,14 @@ bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist, const short mo return true; } -/** - * Apply smooth position to stroke point. - * \param gps: Stroke to smooth - * \param i: Point index - * \param inf: Amount of smoothing to apply - */ -bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf) + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Smooth Positions + * \{ */ + +bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf, const bool smooth_caps) { bGPDspoint *pt = &gps->points[i]; float sco[3] = {0.0f}; @@ -996,7 +958,7 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf) /* Only affect endpoints by a fraction of the normal strength, * to prevent the stroke from shrinking too much */ - if (!is_cyclic && ELEM(i, 0, gps->totpoints - 1)) { + if ((!smooth_caps) && (!is_cyclic && ELEM(i, 0, gps->totpoints - 1))) { inf *= 0.1f; } @@ -1054,12 +1016,12 @@ bool BKE_gpencil_stroke_smooth_point(bGPDstroke *gps, int i, float inf) return true; } -/** - * Apply smooth strength to stroke point. - * \param gps: Stroke to smooth - * \param point_index: Point index - * \param influence: Amount of smoothing to apply - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Smooth Strength + * \{ */ + bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float influence) { bGPDspoint *ptb = &gps->points[point_index]; @@ -1132,12 +1094,12 @@ bool BKE_gpencil_stroke_smooth_strength(bGPDstroke *gps, int point_index, float return true; } -/** - * Apply smooth for thickness to stroke point (use pressure). - * \param gps: Stroke to smooth - * \param point_index: Point index - * \param influence: Amount of smoothing to apply - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Smooth Thickness + * \{ */ + bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float influence) { bGPDspoint *ptb = &gps->points[point_index]; @@ -1209,12 +1171,12 @@ bool BKE_gpencil_stroke_smooth_thickness(bGPDstroke *gps, int point_index, float return true; } -/** - * Apply smooth for UV rotation to stroke point (use pressure). - * \param gps: Stroke to smooth - * \param point_index: Point index - * \param influence: Amount of smoothing to apply - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Smooth UV + * \{ */ + bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influence) { bGPDspoint *ptb = &gps->points[point_index]; @@ -1266,14 +1228,6 @@ bool BKE_gpencil_stroke_smooth_uv(bGPDstroke *gps, int point_index, float influe return true; } -/** - * Get points of stroke always flat to view not affected - * by camera view or view position. - * \param points: Array of grease pencil points (3D) - * \param totpoints: Total of points - * \param points2d: Result array of 2D points - * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0) - */ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float (*points2d)[2], @@ -1348,17 +1302,6 @@ void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, *r_direction = (cross >= 0.0f) ? 1 : -1; } -/** - * Get points of stroke always flat to view not affected by camera view or view position - * using another stroke as reference. - * \param ref_points: Array of reference points (3D) - * \param ref_totpoints: Total reference points - * \param points: Array of points to flat (3D) - * \param totpoints: Total points - * \param points2d: Result array of 2D points - * \param scale: Scale factor - * \param r_direction: Return Concave (-1), Convex (1), or Auto-detect (0) - */ void BKE_gpencil_stroke_2d_flat_ref(const bGPDspoint *ref_points, int ref_totpoints, const bGPDspoint *points, @@ -1482,10 +1425,12 @@ static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2], } } -/** - * Triangulate stroke to generate data for filling areas. - * \param gps: Grease pencil stroke - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Fill Triangulate + * \{ */ + void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps) { BLI_assert(gps->totpoints >= 3); @@ -1545,10 +1490,6 @@ void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps) MEM_SAFE_FREE(uv); } -/** - * Update Stroke UV data. - * \param gps: Grease pencil stroke - */ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) { if (gps == nullptr || gps->totpoints == 0) { @@ -1564,11 +1505,6 @@ void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) } } -/** - * Recalc all internal geometry data for the stroke - * \param gpd: Grease pencil data-block - * \param gps: Grease pencil stroke - */ void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) { if (gps == nullptr) { @@ -1606,12 +1542,6 @@ void BKE_gpencil_stroke_geometry_update(bGPdata *gpd, bGPDstroke *gps) BKE_gpencil_stroke_boundingbox_calc(gps); } -/** - * Calculate grease pencil stroke length. - * \param gps: Grease pencil stroke - * \param use_3d: Set to true to use 3D points - * \return Length of the stroke - */ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) { if (!gps->points || gps->totpoints < 2) { @@ -1632,7 +1562,6 @@ float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) return total_length; } -/** Calculate grease pencil stroke length between points. */ float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps, const int start_index, const int end_index, @@ -1660,10 +1589,6 @@ float BKE_gpencil_stroke_segment_length(const struct bGPDstroke *gps, return total_length; } -/** - * Trim stroke to the first intersection or loop. - * \param gps: Stroke data - */ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 4) { @@ -1756,10 +1681,6 @@ bool BKE_gpencil_stroke_trim(bGPdata *gpd, bGPDstroke *gps) return intersect; } -/** - * Close grease pencil stroke. - * \param gps: Stroke to close - */ bool BKE_gpencil_stroke_close(bGPDstroke *gps) { bGPDspoint *pt1 = nullptr; @@ -1845,13 +1766,12 @@ bool BKE_gpencil_stroke_close(bGPDstroke *gps) return true; } -/** - * Dissolve points in stroke. - * \param gpd: Grease pencil data-block - * \param gpf: Grease pencil frame - * \param gps: Grease pencil stroke - * \param tag: Type of tag for point - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Dissolve Points + * \{ */ + void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, const short tag) { bGPDspoint *pt; @@ -1934,11 +1854,12 @@ void BKE_gpencil_dissolve_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, } } -/** - * Calculate stroke normals. - * \param gps: Grease pencil stroke - * \param r_normal: Return Normal vector normalized - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Normal Calculation + * \{ */ + void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) { if (gps->totpoints < 3) { @@ -1969,18 +1890,12 @@ void BKE_gpencil_stroke_normal(const bGPDstroke *gps, float r_normal[3]) normalize_v3(r_normal); } -/* Stroke Simplify ------------------------------------- */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Simplify + * \{ */ -/** - * Reduce a series of points to a simplified version, but - * maintains the general shape of the series - * - * Ramer - Douglas - Peucker algorithm - * by http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm - * \param gpd: Grease pencil data-block - * \param gps: Grease pencil stroke - * \param epsilon: Epsilon value to define precision of the algorithm - */ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float epsilon) { bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points); @@ -2085,11 +2000,6 @@ void BKE_gpencil_stroke_simplify_adaptive(bGPdata *gpd, bGPDstroke *gps, float e MEM_SAFE_FREE(marked); } -/** - * Simplify alternate vertex of stroke except extremes. - * \param gpd: Grease pencil data-block - * \param gps: Grease pencil stroke - */ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) { if (gps->totpoints < 5) { @@ -2150,13 +2060,6 @@ void BKE_gpencil_stroke_simplify_fixed(bGPdata *gpd, bGPDstroke *gps) MEM_SAFE_FREE(old_dvert); } -/** - * Subdivide a stroke - * \param gpd: Grease pencil data-block - * \param gps: Stroke - * \param level: Level of subdivision - * \param type: Type of subdivision - */ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int type) { bGPDspoint *temp_points; @@ -2269,19 +2172,12 @@ void BKE_gpencil_stroke_subdivide(bGPdata *gpd, bGPDstroke *gps, int level, int BKE_gpencil_stroke_geometry_update(gpd, gps); } -/* Merge by distance ------------------------------------- */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Merge by Distance + * \{ */ -/** - * Reduce a series of points when the distance is below a threshold. - * 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. - */ void BKE_gpencil_stroke_merge_distance(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, @@ -2455,6 +2351,7 @@ static void gpencil_generate_edgeloops(Object *ob, if (me->totedge == 0) { return; } + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(me); /* Arrays for all edge vertices (forward and backward) that form a edge loop. * This is reused for each edge-loop to create gpencil stroke. */ @@ -2469,13 +2366,13 @@ static void gpencil_generate_edgeloops(Object *ob, MEdge *ed = &me->medge[i]; gped = &gp_edges[i]; MVert *mv1 = &me->mvert[ed->v1]; - normal_short_to_float_v3(gped->n1, mv1->no); + copy_v3_v3(gped->n1, vert_normals[ed->v1]); gped->v1 = ed->v1; copy_v3_v3(gped->v1_co, mv1->co); MVert *mv2 = &me->mvert[ed->v2]; - normal_short_to_float_v3(gped->n2, mv2->no); + copy_v3_v3(gped->n2, vert_normals[ed->v2]); gped->v2 = ed->v2; copy_v3_v3(gped->v2_co, mv2->co); @@ -2544,7 +2441,7 @@ static void gpencil_generate_edgeloops(Object *ob, /* Add segment. */ bGPDspoint *pt = &gps_stroke->points[i]; - normal_short_to_float_v3(fpt, mv->no); + copy_v3_v3(fpt, vert_normals[vertex_index]); mul_v3_v3fl(fpt, fpt, offset); add_v3_v3v3(&pt->x, mv->co, fpt); mul_m4_v3(matrix, &pt->x); @@ -2639,22 +2536,6 @@ static void make_element_name(const char *obname, const char *name, const int ma BLI_strncpy_utf8(r_name, str, maxlen); } -/** - * Convert a mesh object to grease pencil stroke. - * - * \param bmain: Main thread pointer. - * \param depsgraph: Original depsgraph. - * \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 edge-loop ends. - * \param thickness: Thickness of the strokes. - * \param offset: Offset along the normals. - * \param matrix: Transformation matrix. - * \param frame_offset: Destination frame number offset. - * \param use_seams: Only export seam edges. - * \param use_faces: Export faces as filled strokes. - */ bool BKE_gpencil_convert_mesh(Main *bmain, Depsgraph *depsgraph, Scene *scene, @@ -2807,11 +2688,6 @@ bool BKE_gpencil_convert_mesh(Main *bmain, return true; } -/** - * Apply grease pencil Transforms. - * \param gpd: Grease pencil data-block - * \param mat: Transformation matrix - */ void BKE_gpencil_transform(bGPdata *gpd, const float mat[4][4]) { if (gpd == nullptr) { @@ -2845,7 +2721,6 @@ 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(const bGPdata *gpd) { int total_points = 0; @@ -2872,7 +2747,6 @@ int BKE_gpencil_stroke_point_count(const bGPdata *gpd) return total_points; } -/* Used for "move only origins" in object_data_transform.c */ void BKE_gpencil_point_coords_get(bGPdata *gpd, GPencilPointCoordinates *elem_data) { if (gpd == nullptr) { @@ -2903,7 +2777,6 @@ 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 == nullptr) { @@ -2937,7 +2810,6 @@ void BKE_gpencil_point_coords_apply(bGPdata *gpd, const GPencilPointCoordinates } } -/* Used for "move only origins" in object_data_transform.c */ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, const GPencilPointCoordinates *elem_data, const float mat[4][4]) @@ -2974,10 +2846,6 @@ void BKE_gpencil_point_coords_apply_with_mat4(bGPdata *gpd, } } -/** - * Set a random color to stroke using vertex color. - * \param gps: Stroke - */ void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps) { BLI_assert(gps->totpoints > 0); @@ -2993,7 +2861,6 @@ void BKE_gpencil_stroke_set_random_color(bGPDstroke *gps) } } -/* Flip stroke. */ void BKE_gpencil_stroke_flip(bGPDstroke *gps) { /* Reverse points. */ @@ -3103,28 +2970,25 @@ static void gpencil_stroke_join_islands(bGPdata *gpd, BKE_gpencil_free_stroke(gps_last); } -/* Split the given stroke into several new strokes, partitioning - * it based on whether the stroke points have a particular flag - * is set (e.g. "GP_SPOINT_SELECT" in most cases, but not always) - * - * The algorithm used here is as follows: - * 1) We firstly identify the number of "islands" of non-tagged points - * which will all end up being in new strokes. - * - In the most extreme case (i.e. every other vert is a 1-vert island), - * we have at most n / 2 islands - * - Once we start having larger islands than that, the number required - * becomes much less - * 2) Each island gets converted to a new stroke - * If the number of points is <= limit, the stroke is deleted - */ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *next_stroke, int tag_flags, - bool select, - int limit) + const bool select, + const bool flat_cap, + const int limit) { + /* The algorithm used here is as follows: + * 1) We firstly identify the number of "islands" of non-tagged points + * which will all end up being in new strokes. + * - In the most extreme case (i.e. every other vert is a 1-vert island), + * we have at most `n / 2` islands + * - Once we start having larger islands than that, the number required + * becomes much less + * 2) Each island gets converted to a new stroke + * If the number of points is <= limit, the stroke is deleted. */ + tGPDeleteIsland *islands = (tGPDeleteIsland *)MEM_callocN( sizeof(tGPDeleteIsland) * (gps->totpoints + 1) / 2, "gp_point_islands"); bool in_island = false; @@ -3171,6 +3035,9 @@ bGPDstroke *BKE_gpencil_stroke_delete_tagged_points(bGPdata *gpd, for (idx = 0; idx < num_islands; idx++) { tGPDeleteIsland *island = &islands[idx]; new_stroke = BKE_gpencil_stroke_duplicate(gps, false, true); + if (flat_cap) { + new_stroke->caps[1 - (idx % 2)] = GP_STROKE_CAP_FLAT; + } /* if cyclic and first stroke, save to join later */ if ((is_cyclic) && (gps_first == nullptr)) { @@ -3426,7 +3293,6 @@ static void gpencil_stroke_copy_point(bGPDstroke *gps, } } -/* Join two strokes using the shortest distance (reorder stroke if necessary ) */ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, bGPDstroke *gps_b, const bool leave_gaps, @@ -3544,7 +3410,7 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, 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); + BKE_gpencil_stroke_smooth_point(gps_a, i, ratio * 0.6f, false); ratio += step; /* In the center, reverse the ratio. */ @@ -3556,7 +3422,6 @@ void BKE_gpencil_stroke_join(bGPDstroke *gps_a, } } -/* Copy the stroke of the frame to all frames selected (except current). */ void BKE_gpencil_stroke_copy_to_keyframes( bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, const bool tail) { @@ -3595,7 +3460,11 @@ void BKE_gpencil_stroke_copy_to_keyframes( BLI_ghash_free(frame_list, nullptr, nullptr); } -/* Stroke Uniform Subdivide ------------------------------------- */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke Uniform Subdivide + * \{ */ struct tSamplePoint { struct tSamplePoint *next, *prev; @@ -3615,7 +3484,7 @@ struct 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 = (tSamplePoint *)MEM_callocN(sizeof(tSamplePoint), __func__); + tSamplePoint *new_pt = MEM_cnew<tSamplePoint>(__func__); copy_v3_v3(&new_pt->x, &pt->x); new_pt->pressure = pt->pressure; new_pt->strength = pt->strength; @@ -3638,22 +3507,13 @@ 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 = (tSampleEdge *)MEM_callocN(sizeof(tSampleEdge), __func__); + tSampleEdge *new_edge = MEM_cnew<tSampleEdge>(__func__); new_edge->from = from; new_edge->to = to; new_edge->length_sq = len_squared_v3v3(&from->x, &to->x); return new_edge; } -/** - * Subdivide the grease pencil stroke so the number of points is target_number. - * Does not change the shape of the stroke. The new points will be distributed as - * uniformly as possible by repeatedly subdividing the current longest edge. - * - * \param gps: The stroke to be up-sampled. - * \param target_number: The number of points the up-sampled stroke should have. - * \param select: Select/Deselect the stroke. - */ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd, bGPDstroke *gps, const uint32_t target_number, @@ -3703,7 +3563,7 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd, tSamplePoint *sp_next = se->to; /* Subdivide the edge. */ - tSamplePoint *new_sp = (tSamplePoint *)MEM_callocN(sizeof(tSamplePoint), __func__); + tSamplePoint *new_sp = MEM_cnew<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); @@ -3789,12 +3649,6 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd, BKE_gpencil_stroke_geometry_update(gpd, gps); } -/** - * 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 - */ void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d, bGPDstroke *gps, const float diff_mat[4][4]) @@ -3808,12 +3662,6 @@ 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 - */ void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d, bGPDstroke *gps, const float diff_mat[4][4]) @@ -3828,8 +3676,11 @@ void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d, } } -/* ----------------------------------------------------------------------------- */ -/* Stroke to perimeter */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Stroke to Perimeter + * \{ */ struct tPerimeterPoint { struct tPerimeterPoint *next, *prev; @@ -3838,7 +3689,7 @@ struct tPerimeterPoint { static tPerimeterPoint *new_perimeter_point(const float pt[3]) { - tPerimeterPoint *new_pt = (tPerimeterPoint *)MEM_callocN(sizeof(tPerimeterPoint), __func__); + tPerimeterPoint *new_pt = MEM_cnew<tPerimeterPoint>(__func__); copy_v3_v3(&new_pt->x, pt); return new_pt; } @@ -4007,8 +3858,8 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, float defaultpixsize = 1000.0f / gpd->pixfactor; float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f; - ListBase *perimeter_right_side = (ListBase *)MEM_callocN(sizeof(ListBase), __func__); - ListBase *perimeter_left_side = (ListBase *)MEM_callocN(sizeof(ListBase), __func__); + ListBase *perimeter_right_side = MEM_cnew<ListBase>(__func__); + ListBase *perimeter_left_side = MEM_cnew<ListBase>(__func__); int num_perimeter_points = 0; bGPDspoint *first = &gps->points[0]; @@ -4234,12 +4085,6 @@ static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd, return perimeter_list; } -/** - * Calculates the perimeter of a stroke projected from the view and - * returns it as a new stroke. - * \param subdivisions: Number of subdivisions for the start and end caps - * \return: bGPDstroke pointer to stroke perimeter - */ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d, bGPdata *gpd, const bGPDlayer *gpl, @@ -4306,7 +4151,6 @@ bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d, return perimeter_stroke; } -/** Get average pressure. */ float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps) { @@ -4323,7 +4167,6 @@ float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps) return tot / (float)gps->totpoints; } -/** Check if the thickness of the stroke is constant. */ bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps) { if (gps->totpoints == 1) { @@ -4340,4 +4183,5 @@ bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps) return true; } + /** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index a6164340477..74db151261f 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -36,6 +36,7 @@ #include "DNA_armature_types.h" #include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" #include "DNA_object_types.h" @@ -50,7 +51,9 @@ #include "BKE_lib_id.h" #include "BKE_lib_query.h" #include "BKE_material.h" +#include "BKE_modifier.h" #include "BKE_object.h" +#include "BKE_shrinkwrap.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -76,44 +79,76 @@ static GpencilVirtualModifierData virtualModifierCommonData; * each loop over all the geometry being evaluated. */ -/** - * Init grease pencil lattice deform data. - * \param ob: Grease pencil object - */ -void BKE_gpencil_lattice_init(Object *ob) +void BKE_gpencil_cache_data_init(Depsgraph *depsgraph, Object *ob) { LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { - if (md->type == eGpencilModifierType_Lattice) { - LatticeGpencilModifierData *mmd = (LatticeGpencilModifierData *)md; - Object *latob = NULL; + switch (md->type) { + case eGpencilModifierType_Lattice: { + LatticeGpencilModifierData *mmd = (LatticeGpencilModifierData *)md; + Object *latob = NULL; + + latob = mmd->object; + if ((!latob) || (latob->type != OB_LATTICE)) { + return; + } + if (mmd->cache_data) { + BKE_lattice_deform_data_destroy(mmd->cache_data); + } - latob = mmd->object; - if ((!latob) || (latob->type != OB_LATTICE)) { - return; + /* init deform data */ + mmd->cache_data = BKE_lattice_deform_data_create(latob, ob); + break; } - if (mmd->cache_data) { - BKE_lattice_deform_data_destroy(mmd->cache_data); + case eGpencilModifierType_Shrinkwrap: { + ShrinkwrapGpencilModifierData *mmd = (ShrinkwrapGpencilModifierData *)md; + ob = mmd->target; + if (!ob) { + return; + } + if (mmd->cache_data) { + BKE_shrinkwrap_free_tree(mmd->cache_data); + MEM_SAFE_FREE(mmd->cache_data); + } + Object *ob_target = DEG_get_evaluated_object(depsgraph, ob); + Mesh *target = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob_target, false); + mmd->cache_data = MEM_callocN(sizeof(ShrinkwrapTreeData), __func__); + if (BKE_shrinkwrap_init_tree( + mmd->cache_data, target, mmd->shrink_type, mmd->shrink_mode, false)) { + } + else { + MEM_SAFE_FREE(mmd->cache_data); + } + break; } - /* init deform data */ - mmd->cache_data = BKE_lattice_deform_data_create(latob, ob); + default: + break; } } } -/** - * Clear grease pencil lattice deform data. - * \param ob: Grease pencil object - */ -void BKE_gpencil_lattice_clear(Object *ob) +void BKE_gpencil_cache_data_clear(Object *ob) { LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { - if (md->type == eGpencilModifierType_Lattice) { - LatticeGpencilModifierData *mmd = (LatticeGpencilModifierData *)md; - if ((mmd) && (mmd->cache_data)) { - BKE_lattice_deform_data_destroy(mmd->cache_data); - mmd->cache_data = NULL; + switch (md->type) { + case eGpencilModifierType_Lattice: { + LatticeGpencilModifierData *mmd = (LatticeGpencilModifierData *)md; + if ((mmd) && (mmd->cache_data)) { + BKE_lattice_deform_data_destroy(mmd->cache_data); + mmd->cache_data = NULL; + } + break; + } + case eGpencilModifierType_Shrinkwrap: { + ShrinkwrapGpencilModifierData *mmd = (ShrinkwrapGpencilModifierData *)md; + if ((mmd) && (mmd->cache_data)) { + BKE_shrinkwrap_free_tree(mmd->cache_data); + MEM_SAFE_FREE(mmd->cache_data); + } + break; } + default: + break; } } } @@ -121,8 +156,6 @@ void BKE_gpencil_lattice_clear(Object *ob) /* *************************************************** */ /* Modifier Methods - Evaluation Loops, etc. */ -/* This is to include things that are not modifiers in the evaluation of the modifier stack, for - * example parenting to an armature or lattice without having a real modifier. */ GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist( const Object *ob, GpencilVirtualModifierData *UNUSED(virtualModifierData)) { @@ -150,11 +183,6 @@ GpencilModifierData *BKE_gpencil_modifiers_get_virtual_modifierlist( return md; } -/** - * Check if object has grease pencil Geometry modifiers. - * \param ob: Grease pencil object - * \return True if exist - */ bool BKE_gpencil_has_geometry_modifiers(Object *ob) { LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { @@ -167,11 +195,6 @@ bool BKE_gpencil_has_geometry_modifiers(Object *ob) return false; } -/** - * Check if object has grease pencil Time modifiers. - * \param ob: Grease pencil object - * \return True if exist - */ bool BKE_gpencil_has_time_modifiers(Object *ob) { LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { @@ -184,11 +207,6 @@ bool BKE_gpencil_has_time_modifiers(Object *ob) return false; } -/** - * Check if object has grease pencil transform stroke modifiers. - * \param ob: Grease pencil object - * \return True if exist - */ bool BKE_gpencil_has_transform_modifiers(Object *ob) { LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { @@ -258,7 +276,6 @@ bool BKE_gpencil_is_first_lineart_in_stack(const Object *ob, const GpencilModifi return false; } -/* Get Time modifier frame number. */ int BKE_gpencil_time_modifier_cfra(Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -292,11 +309,6 @@ int BKE_gpencil_time_modifier_cfra(Depsgraph *depsgraph, return nfra; } -/** - * Set current grease pencil active frame. - * \param depsgraph: Current depsgraph - * \param gpd: Grease pencil data-block. - */ void BKE_gpencil_frame_active_set(Depsgraph *depsgraph, bGPdata *gpd) { DEG_debug_print_eval(depsgraph, __func__, gpd->id.name, gpd); @@ -320,9 +332,6 @@ void BKE_gpencil_frame_active_set(Depsgraph *depsgraph, bGPdata *gpd) } } -/** - * Initialize grease pencil modifier. - */ void BKE_gpencil_modifier_init(void) { /* Initialize modifier types */ @@ -346,11 +355,6 @@ void BKE_gpencil_modifier_init(void) #endif } -/** - * Create new grease pencil modifier. - * \param type: Type of modifier - * \return New modifier pointer - */ GpencilModifierData *BKE_gpencil_modifier_new(int type) { const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(type); @@ -386,11 +390,6 @@ static void modifier_free_data_id_us_cb(void *UNUSED(userData), } } -/** - * Free grease pencil modifier data - * \param md: Modifier data - * \param flag: Flags - */ void BKE_gpencil_modifier_free_ex(GpencilModifierData *md, const int flag) { const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); @@ -411,16 +410,11 @@ void BKE_gpencil_modifier_free_ex(GpencilModifierData *md, const int flag) MEM_freeN(md); } -/** - * Free grease pencil modifier data - * \param md: Modifier data - */ void BKE_gpencil_modifier_free(GpencilModifierData *md) { BKE_gpencil_modifier_free_ex(md, 0); } -/* check unique name */ bool BKE_gpencil_modifier_unique_name(ListBase *modifiers, GpencilModifierData *gmd) { if (modifiers && gmd) { @@ -435,11 +429,6 @@ bool BKE_gpencil_modifier_unique_name(ListBase *modifiers, GpencilModifierData * return false; } -/** - * Check if grease pencil modifier depends on time. - * \param md: Modifier data - * \return True if depends on time - */ bool BKE_gpencil_modifier_depends_ontime(GpencilModifierData *md) { const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); @@ -447,11 +436,6 @@ bool BKE_gpencil_modifier_depends_ontime(GpencilModifierData *md) return mti->dependsOnTime && mti->dependsOnTime(md); } -/** - * Get grease pencil modifier information. - * \param type: Type of modifier - * \return Pointer to type - */ const GpencilModifierTypeInfo *BKE_gpencil_modifier_get_info(GpencilModifierType type) { /* type unsigned, no need to check < 0 */ @@ -463,12 +447,6 @@ const GpencilModifierTypeInfo *BKE_gpencil_modifier_get_info(GpencilModifierType return NULL; } -/** - * Get the idname of the modifier type's panel, which was defined in the #panelRegister callback. - * - * \param type: Type of modifier - * \param r_idname: ID name - */ void BKE_gpencil_modifierType_panel_id(GpencilModifierType type, char *r_idname) { const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(type); @@ -482,11 +460,6 @@ void BKE_gpencil_modifier_panel_expand(GpencilModifierData *md) md->ui_expand_flag |= UI_PANEL_DATA_EXPAND_ROOT; } -/** - * Generic grease pencil modifier copy data. - * \param md_src: Source modifier data - * \param md_dst: Target modifier data - */ void BKE_gpencil_modifier_copydata_generic(const GpencilModifierData *md_src, GpencilModifierData *md_dst) { @@ -516,12 +489,6 @@ 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 - * \param flag: Flags - */ void BKE_gpencil_modifier_copydata_ex(GpencilModifierData *md, GpencilModifierData *target, const int flag) @@ -543,11 +510,6 @@ void BKE_gpencil_modifier_copydata_ex(GpencilModifierData *md, } } -/** - * Copy grease pencil modifier data. - * \param md: Source modifier data - * \param target: Target modifier data - */ void BKE_gpencil_modifier_copydata(GpencilModifierData *md, GpencilModifierData *target) { BKE_gpencil_modifier_copydata_ex(md, target, 0); @@ -566,19 +528,14 @@ GpencilModifierData *BKE_gpencil_modifiers_findby_type(Object *ob, GpencilModifi return md; } -/** - * Set grease pencil modifier error. - * \param md: Modifier data - * \param _format: Format - */ -void BKE_gpencil_modifier_set_error(GpencilModifierData *md, const char *_format, ...) +void BKE_gpencil_modifier_set_error(GpencilModifierData *md, const char *format, ...) { char buffer[512]; va_list ap; - const char *format = TIP_(_format); + const char *format_tip = TIP_(format); - va_start(ap, _format); - vsnprintf(buffer, sizeof(buffer), format, ap); + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format_tip, ap); va_end(ap); buffer[sizeof(buffer) - 1] = '\0'; @@ -591,12 +548,6 @@ void BKE_gpencil_modifier_set_error(GpencilModifierData *md, const char *_format CLOG_STR_ERROR(&LOG, md->error); } -/** - * Check whether given modifier is not local (i.e. from linked data) when the object is a library - * override. - * - * \param gmd: May be NULL, in which case we consider it as a non-local modifier case. - */ bool BKE_gpencil_modifier_is_nonlocal_in_liboverride(const Object *ob, const GpencilModifierData *gmd) { @@ -604,12 +555,6 @@ bool BKE_gpencil_modifier_is_nonlocal_in_liboverride(const Object *ob, (gmd == NULL || (gmd->flag & eGpencilModifierFlag_OverrideLibrary_Local) == 0)); } -/** - * Link grease pencil modifier related IDs. - * \param ob: Grease pencil object - * \param walk: Walk option - * \param userData: User data - */ void BKE_gpencil_modifiers_foreach_ID_link(Object *ob, GreasePencilIDWalkFunc walk, void *userData) { GpencilModifierData *md = ob->greasepencil_modifiers.first; @@ -623,12 +568,6 @@ void BKE_gpencil_modifiers_foreach_ID_link(Object *ob, GreasePencilIDWalkFunc wa } } -/** - * Link grease pencil modifier related Texts. - * \param ob: Grease pencil object - * \param walk: Walk option - * \param userData: User data - */ void BKE_gpencil_modifiers_foreach_tex_link(Object *ob, GreasePencilTexWalkFunc walk, void *userData) @@ -644,12 +583,6 @@ void BKE_gpencil_modifiers_foreach_tex_link(Object *ob, } } -/** - * Find grease pencil modifier by name. - * \param ob: Grease pencil object - * \param name: Name to find - * \return Pointer to modifier - */ GpencilModifierData *BKE_gpencil_modifiers_findby_name(Object *ob, const char *name) { return BLI_findstring(&(ob->greasepencil_modifiers), name, offsetof(GpencilModifierData, name)); @@ -677,14 +610,6 @@ static int gpencil_remap_time_get(Depsgraph *depsgraph, Scene *scene, Object *ob return remap_cfra; } -/** - * Get the current frame re-timed with time modifiers. - * \param depsgraph: Current depsgraph. - * \param scene: Current scene - * \param ob: Grease pencil object - * \param gpl: Grease pencil layer - * \return New frame number - */ bGPDframe *BKE_gpencil_frame_retime_get(Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -753,12 +678,6 @@ static bGPdata *gpencil_copy_for_eval(bGPdata *gpd) return result; } -/** - * Prepare grease pencil eval data for modifiers - * \param depsgraph: Current depsgraph - * \param scene: Current scene - * \param ob: Grease pencil object - */ void BKE_gpencil_prepare_eval_data(Depsgraph *depsgraph, Scene *scene, Object *ob) { bGPdata *gpd_eval = (bGPdata *)ob->data; @@ -808,12 +727,6 @@ void BKE_gpencil_prepare_eval_data(Depsgraph *depsgraph, Scene *scene, Object *o BKE_gpencil_update_orig_pointers(ob_orig, ob); } -/** - * Calculate gpencil modifiers. - * \param depsgraph: Current depsgraph - * \param scene: Current scene - * \param ob: Grease pencil object - */ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) { bGPdata *gpd = (bGPdata *)ob->data; @@ -829,7 +742,7 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) } /* Init general modifiers data. */ - BKE_gpencil_lattice_init(ob); + BKE_gpencil_cache_data_init(depsgraph, ob); const bool time_remap = BKE_gpencil_has_time_modifiers(ob); bool is_first_lineart = true; @@ -872,8 +785,8 @@ void BKE_gpencil_modifiers_calc(Depsgraph *depsgraph, Scene *scene, Object *ob) } } - /* Clear any lattice data. */ - BKE_gpencil_lattice_clear(ob); + /* Clear any cache data. */ + BKE_gpencil_cache_data_clear(ob); MOD_lineart_clear_cache(&gpd->runtime.lineart_cache); } @@ -1031,6 +944,10 @@ void BKE_gpencil_modifier_blend_read_data(BlendDataReader *reader, ListBase *lb) gpmd->segments[i].dmd = gpmd; } } + if (md->type == eGpencilModifierType_Shrinkwrap) { + ShrinkwrapGpencilModifierData *gpmd = (ShrinkwrapGpencilModifierData *)md; + gpmd->cache_data = NULL; + } } } diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.cc index cf346e9cac7..b7ba159f631 100644 --- a/source/blender/blenkernel/intern/hair.c +++ b/source/blender/blenkernel/intern/hair.cc @@ -18,6 +18,9 @@ * \ingroup bke */ +#include <cmath> +#include <cstring> + #include "MEM_guardedalloc.h" #include "DNA_defaults.h" @@ -26,7 +29,8 @@ #include "DNA_object_types.h" #include "BLI_listbase.h" -#include "BLI_math.h" +#include "BLI_math_base.h" +#include "BLI_math_vec_types.hh" #include "BLI_rand.h" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -49,6 +53,8 @@ #include "BLO_read_write.h" +using blender::float3; + static const char *HAIR_ATTR_POSITION = "position"; static const char *HAIR_ATTR_RADIUS = "radius"; @@ -67,10 +73,10 @@ static void hair_init_data(ID *id) CustomData_reset(&hair->cdata); CustomData_add_layer_named( - &hair->pdata, CD_PROP_FLOAT3, CD_CALLOC, NULL, hair->totpoint, HAIR_ATTR_POSITION); + &hair->pdata, CD_PROP_FLOAT3, CD_CALLOC, nullptr, hair->totpoint, HAIR_ATTR_POSITION); CustomData_add_layer_named( - &hair->pdata, CD_PROP_FLOAT, CD_CALLOC, NULL, hair->totpoint, HAIR_ATTR_RADIUS); - CustomData_add_layer(&hair->cdata, CD_HAIRCURVE, CD_CALLOC, NULL, hair->totcurve); + &hair->pdata, CD_PROP_FLOAT, CD_CALLOC, nullptr, hair->totpoint, HAIR_ATTR_RADIUS); + CustomData_add_layer(&hair->cdata, CD_HAIRCURVE, CD_CALLOC, nullptr, hair->totcurve); BKE_hair_update_customdata_pointers(hair); hair_random(hair); @@ -80,14 +86,14 @@ static void hair_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, co { Hair *hair_dst = (Hair *)id_dst; const Hair *hair_src = (const Hair *)id_src; - hair_dst->mat = MEM_dupallocN(hair_dst->mat); + hair_dst->mat = static_cast<Material **>(MEM_dupallocN(hair_src->mat)); const eCDAllocType alloc_type = (flag & LIB_ID_COPY_CD_REFERENCE) ? CD_REFERENCE : CD_DUPLICATE; CustomData_copy(&hair_src->pdata, &hair_dst->pdata, CD_MASK_ALL, alloc_type, hair_dst->totpoint); CustomData_copy(&hair_src->cdata, &hair_dst->cdata, CD_MASK_ALL, alloc_type, hair_dst->totcurve); BKE_hair_update_customdata_pointers(hair_dst); - hair_dst->batch_cache = NULL; + hair_dst->batch_cache = nullptr; } static void hair_free_data(ID *id) @@ -107,7 +113,7 @@ 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); } } @@ -115,8 +121,8 @@ static void hair_blend_write(BlendWriter *writer, ID *id, const void *id_address { Hair *hair = (Hair *)id; - CustomDataLayer *players = NULL, players_buff[CD_TEMP_CHUNK_SIZE]; - CustomDataLayer *clayers = NULL, clayers_buff[CD_TEMP_CHUNK_SIZE]; + CustomDataLayer *players = nullptr, players_buff[CD_TEMP_CHUNK_SIZE]; + CustomDataLayer *clayers = nullptr, 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)); @@ -174,31 +180,33 @@ static void hair_blend_read_expand(BlendExpander *expander, ID *id) } IDTypeInfo IDType_ID_HA = { - .id_code = ID_HA, - .id_filter = FILTER_ID_HA, - .main_listbase_index = INDEX_ID_HA, - .struct_size = sizeof(Hair), - .name = "Hair", - .name_plural = "hairs", - .translation_context = BLT_I18NCONTEXT_ID_HAIR, - .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, - - .init_data = hair_init_data, - .copy_data = hair_copy_data, - .free_data = hair_free_data, - .make_local = NULL, - .foreach_id = hair_foreach_id, - .foreach_cache = NULL, - .owner_get = NULL, - - .blend_write = hair_blend_write, - .blend_read_data = hair_blend_read_data, - .blend_read_lib = hair_blend_read_lib, - .blend_read_expand = hair_blend_read_expand, - - .blend_read_undo_preserve = NULL, - - .lib_override_apply_post = NULL, + /*id_code */ ID_HA, + /*id_filter */ FILTER_ID_HA, + /*main_listbase_index */ INDEX_ID_HA, + /*struct_size */ sizeof(Hair), + /*name */ "Hair", + /*name_plural */ "hairs", + /*translation_context */ BLT_I18NCONTEXT_ID_HAIR, + /*flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /*asset_type_info */ nullptr, + + /*init_data */ hair_init_data, + /*copy_data */ hair_copy_data, + /*free_data */ hair_free_data, + /*make_local */ nullptr, + /*foreach_id */ hair_foreach_id, + /*foreach_cache */ nullptr, + /*foreach_path */ nullptr, + /*owner_get */ nullptr, + + /*blend_write */ hair_blend_write, + /*blend_read_data */ hair_blend_read_data, + /*blend_read_lib */ hair_blend_read_lib, + /*blend_read_expand */ hair_blend_read_expand, + + /*blend_read_undo_preserve */ nullptr, + + /*lib_override_apply_post */ nullptr, }; static void hair_random(Hair *hair) @@ -248,7 +256,7 @@ static void hair_random(Hair *hair) void *BKE_hair_add(Main *bmain, const char *name) { - Hair *hair = BKE_id_new(bmain, ID_HA, name); + Hair *hair = static_cast<Hair *>(BKE_id_new(bmain, ID_HA, name)); return hair; } @@ -256,14 +264,14 @@ void *BKE_hair_add(Main *bmain, const char *name) BoundBox *BKE_hair_boundbox_get(Object *ob) { BLI_assert(ob->type == OB_HAIR); - Hair *hair = ob->data; + Hair *hair = static_cast<Hair *>(ob->data); - if (ob->runtime.bb != NULL && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { + if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) { return ob->runtime.bb; } - if (ob->runtime.bb == NULL) { - ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "hair boundbox"); + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = MEM_cnew<BoundBox>(__func__); float min[3], max[3]; INIT_MINMAX(min, max); @@ -287,10 +295,12 @@ BoundBox *BKE_hair_boundbox_get(Object *ob) void BKE_hair_update_customdata_pointers(Hair *hair) { - hair->co = CustomData_get_layer_named(&hair->pdata, CD_PROP_FLOAT3, HAIR_ATTR_POSITION); - hair->radius = CustomData_get_layer_named(&hair->pdata, CD_PROP_FLOAT, HAIR_ATTR_RADIUS); - hair->curves = CustomData_get_layer(&hair->cdata, CD_HAIRCURVE); - hair->mapping = CustomData_get_layer(&hair->cdata, CD_HAIRMAPPING); + hair->co = (float(*)[3])CustomData_get_layer_named( + &hair->pdata, CD_PROP_FLOAT3, HAIR_ATTR_POSITION); + hair->radius = (float *)CustomData_get_layer_named( + &hair->pdata, CD_PROP_FLOAT, HAIR_ATTR_RADIUS); + hair->curves = (HairCurve *)CustomData_get_layer(&hair->cdata, CD_HAIRCURVE); + hair->mapping = (HairMaping *)CustomData_get_layer(&hair->cdata, CD_HAIRMAPPING); } bool BKE_hair_customdata_required(Hair *UNUSED(hair), CustomDataLayer *layer) @@ -302,10 +312,10 @@ bool BKE_hair_customdata_required(Hair *UNUSED(hair), CustomDataLayer *layer) Hair *BKE_hair_new_for_eval(const Hair *hair_src, int totpoint, int totcurve) { - Hair *hair_dst = BKE_id_new_nomain(ID_HA, NULL); + Hair *hair_dst = static_cast<Hair *>(BKE_id_new_nomain(ID_HA, nullptr)); STRNCPY(hair_dst->id.name, hair_src->id.name); - hair_dst->mat = MEM_dupallocN(hair_src->mat); + hair_dst->mat = static_cast<Material **>(MEM_dupallocN(hair_src->mat)); hair_dst->totcol = hair_src->totcol; hair_dst->totpoint = totpoint; @@ -325,7 +335,7 @@ Hair *BKE_hair_copy_for_eval(Hair *hair_src, bool reference) flags |= LIB_ID_COPY_CD_REFERENCE; } - Hair *result = (Hair *)BKE_id_copy_ex(NULL, &hair_src->id, NULL, flags); + Hair *result = (Hair *)BKE_id_copy_ex(nullptr, &hair_src->id, nullptr, flags); return result; } @@ -349,7 +359,7 @@ static Hair *hair_evaluate_modifiers(struct Depsgraph *depsgraph, /* Evaluate modifiers. */ for (; md; md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast<ModifierType>(md->type)); if (!BKE_modifier_is_enabled(scene, md, required_mode)) { continue; @@ -368,7 +378,7 @@ static Hair *hair_evaluate_modifiers(struct Depsgraph *depsgraph, BKE_hair_update_customdata_pointers(hair); /* Created deformed coordinates array on demand. */ - mti->deformVerts(md, &mectx, NULL, hair->co, hair->totpoint); + mti->deformVerts(md, &mectx, nullptr, hair->co, hair->totpoint); } else if (mti->modifyHair) { /* Ensure we are not modifying the input. */ @@ -381,7 +391,7 @@ static Hair *hair_evaluate_modifiers(struct Depsgraph *depsgraph, if (hair_next && hair_next != hair) { /* If the modifier returned a new hair, release the old one. */ if (hair != hair_input) { - BKE_id_free(NULL, hair); + BKE_id_free(nullptr, hair); } hair = hair_next; } @@ -397,7 +407,7 @@ void BKE_hair_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Obje BKE_object_free_derived_caches(object); /* Evaluate modifiers. */ - Hair *hair = object->data; + Hair *hair = static_cast<Hair *>(object->data); Hair *hair_eval = hair_evaluate_modifiers(depsgraph, scene, object, hair); /* Assign evaluated object. */ @@ -406,8 +416,9 @@ void BKE_hair_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Obje } /* Draw Cache */ -void (*BKE_hair_batch_cache_dirty_tag_cb)(Hair *hair, int mode) = NULL; -void (*BKE_hair_batch_cache_free_cb)(Hair *hair) = NULL; + +void (*BKE_hair_batch_cache_dirty_tag_cb)(Hair *hair, int mode) = nullptr; +void (*BKE_hair_batch_cache_free_cb)(Hair *hair) = nullptr; void BKE_hair_batch_cache_dirty_tag(Hair *hair, int mode) { diff --git a/source/blender/blenkernel/intern/icons.cc b/source/blender/blenkernel/intern/icons.cc index 97c742b1ec1..059caaa27f9 100644 --- a/source/blender/blenkernel/intern/icons.cc +++ b/source/blender/blenkernel/intern/icons.cc @@ -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" @@ -209,7 +210,7 @@ void BKE_icons_init(int first_dyn_id) } } -void BKE_icons_free(void) +void BKE_icons_free() { BLI_assert(BLI_thread_is_main()); @@ -226,7 +227,7 @@ void BKE_icons_free(void) BLI_linklist_lockfree_free(&g_icon_delete_queue, MEM_freeN); } -void BKE_icons_deferred_free(void) +void BKE_icons_deferred_free() { std::scoped_lock lock(gIconMutex); @@ -250,7 +251,7 @@ static PreviewImage *previewimg_create_ex(size_t deferred_data_size) } for (int i = 0; i < NUM_ICON_SIZES; i++) { - prv_img->flag[i] |= (PRV_CHANGED | PRV_UNFINISHED); + prv_img->flag[i] |= PRV_CHANGED; prv_img->changed_timestamp[i] = 0; } return prv_img; @@ -270,7 +271,7 @@ static PreviewImage *previewimg_deferred_create(const char *path, int source) return prv; } -PreviewImage *BKE_previewimg_create(void) +PreviewImage *BKE_previewimg_create() { return previewimg_create_ex(0); } @@ -307,7 +308,7 @@ void BKE_previewimg_clear_single(struct PreviewImage *prv, enum eIconSizes size) GPU_texture_free(prv->gputexture[size]); } prv->h[size] = prv->w[size] = 0; - prv->flag[size] |= (PRV_CHANGED | PRV_UNFINISHED); + prv->flag[size] |= PRV_CHANGED; prv->flag[size] &= ~PRV_USER_EDITED; prv->changed_timestamp[size] = 0; } @@ -335,10 +336,6 @@ PreviewImage *BKE_previewimg_copy(const PreviewImage *prv) return prv_img; } -/** - * Duplicate preview image from \a id and clear icon_id, - * to be used by datablock copy functions. - */ void BKE_previewimg_id_copy(ID *new_id, const ID *old_id) { PreviewImage **old_prv_p = BKE_previewimg_id_get_p(old_id); @@ -364,17 +361,18 @@ PreviewImage **BKE_previewimg_id_get_p(const ID *id) return &((id_struct *)id)->preview; \ } \ ((void)0) + ID_PRV_CASE(ID_OB, Object); 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_NT, bNodeTree); #undef ID_PRV_CASE default: break; @@ -458,9 +456,6 @@ PreviewImage *BKE_previewimg_cached_get(const char *name) return (PreviewImage *)BLI_ghash_lookup(gCachedPreviews, name); } -/** - * Generate an empty PreviewImage, if not yet existing. - */ PreviewImage *BKE_previewimg_cached_ensure(const char *name) { BLI_assert(BLI_thread_is_main()); @@ -478,10 +473,6 @@ PreviewImage *BKE_previewimg_cached_ensure(const char *name) return prv; } -/** - * Generate a PreviewImage from given file path, using thumbnails management, if not yet existing. - * Does not actually generate the preview, #BKE_previewimg_ensure() must be called for that. - */ PreviewImage *BKE_previewimg_cached_thumbnail_read(const char *name, const char *path, const int source, @@ -536,10 +527,6 @@ void BKE_previewimg_cached_release(const char *name) BKE_previewimg_deferred_release(prv); } -/** - * Handle deferred (lazy) loading/generation of preview image, if needed. - * For now, only used with file thumbnails. - */ void BKE_previewimg_ensure(PreviewImage *prv, const int size) { if ((prv->tag & PRV_TAG_DEFFERED) != 0) { @@ -563,7 +550,7 @@ void BKE_previewimg_ensure(PreviewImage *prv, const int size) prv->w[ICON_SIZE_PREVIEW] = thumb->x; prv->h[ICON_SIZE_PREVIEW] = thumb->y; prv->rect[ICON_SIZE_PREVIEW] = (uint *)MEM_dupallocN(thumb->rect); - prv->flag[ICON_SIZE_PREVIEW] &= ~(PRV_CHANGED | PRV_USER_EDITED | PRV_UNFINISHED); + prv->flag[ICON_SIZE_PREVIEW] &= ~(PRV_CHANGED | PRV_USER_EDITED | PRV_RENDERING); } if (do_icon) { if (thumb->x > thumb->y) { @@ -582,7 +569,7 @@ void BKE_previewimg_ensure(PreviewImage *prv, const int size) prv->w[ICON_SIZE_ICON] = icon_w; prv->h[ICON_SIZE_ICON] = icon_h; prv->rect[ICON_SIZE_ICON] = (uint *)MEM_dupallocN(thumb->rect); - prv->flag[ICON_SIZE_ICON] &= ~(PRV_CHANGED | PRV_USER_EDITED | PRV_UNFINISHED); + prv->flag[ICON_SIZE_ICON] &= ~(PRV_CHANGED | PRV_USER_EDITED | PRV_RENDERING); } IMB_freeImBuf(thumb); } @@ -590,10 +577,6 @@ void BKE_previewimg_ensure(PreviewImage *prv, const int size) } } -/** - * Create an #ImBuf holding a copy of the preview image buffer in \a prv. - * \note The returned image buffer has to be free'd (#IMB_freeImBuf()). - */ ImBuf *BKE_previewimg_to_imbuf(PreviewImage *prv, const int size) { const unsigned int w = prv->w[size]; @@ -614,12 +597,12 @@ ImBuf *BKE_previewimg_to_imbuf(PreviewImage *prv, const int size) void BKE_previewimg_finish(PreviewImage *prv, const int size) { /* Previews may be calculated on a thread. */ - atomic_fetch_and_and_int16(&prv->flag[size], ~PRV_UNFINISHED); + atomic_fetch_and_and_int16(&prv->flag[size], ~PRV_RENDERING); } bool BKE_previewimg_is_finished(const PreviewImage *prv, const int size) { - return (prv->flag[size] & PRV_UNFINISHED) == 0; + return (prv->flag[size] & PRV_RENDERING) == 0; } void BKE_previewimg_blend_write(BlendWriter *writer, const PreviewImage *prv) @@ -653,16 +636,11 @@ void BKE_previewimg_blend_read(BlendDataReader *reader, PreviewImage *prv) BLO_read_data_address(reader, &prv->rect[i]); } prv->gputexture[i] = nullptr; - /* For now consider previews read from file as finished to not confuse File Browser preview - * loading. That could be smarter and check if there's a preview job running instead. - * If the preview is tagged as changed, it needs to be updated anyway, so don't remove the tag. - */ - if ((prv->flag[i] & PRV_CHANGED) == 0) { - BKE_previewimg_finish(prv, i); - } - else { - /* Only for old files that didn't write the flag. */ - prv->flag[i] |= PRV_UNFINISHED; + + /* PRV_RENDERING is a runtime only flag currently, but don't mess with it on undo! It gets + * special handling in #memfile_undosys_restart_unfinished_id_previews() then. */ + if (!BLO_read_data_is_undo(reader)) { + prv->flag[i] &= ~PRV_RENDERING; } } prv->icon_id = 0; @@ -692,7 +670,7 @@ void BKE_icon_changed(const int icon_id) /* If we have previews, they all are now invalid changed. */ if (p_prv && *p_prv) { for (int i = 0; i < NUM_ICON_SIZES; i++) { - (*p_prv)->flag[i] |= (PRV_CHANGED | PRV_UNFINISHED); + (*p_prv)->flag[i] |= PRV_CHANGED; (*p_prv)->changed_timestamp[i]++; } } @@ -799,9 +777,6 @@ int BKE_icon_gplayer_color_ensure(bGPDlayer *gpl) return icon_gplayer_color_ensure_create_icon(gpl); } -/** - * Return icon id of given preview, or create new icon if not found. - */ int BKE_icon_preview_ensure(ID *id, PreviewImage *preview) { if (!preview || G.background) { @@ -842,11 +817,6 @@ int BKE_icon_preview_ensure(ID *id, PreviewImage *preview) return preview->icon_id; } -/** - * Create an icon as owner or \a ibuf. The icon-ID is not stored in \a ibuf, it needs to be stored - * separately. - * \note Transforms ownership of \a ibuf to the newly created icon. - */ int BKE_icon_imbuf_create(ImBuf *ibuf) { int icon_id = get_next_free_id(); @@ -928,9 +898,6 @@ void BKE_icon_id_delete(struct ID *id) BLI_ghash_remove(gIcons, POINTER_FROM_INT(icon_id), nullptr, icon_free); } -/** - * Remove icon and free data. - */ bool BKE_icon_delete(const int icon_id) { if (icon_id == 0) { @@ -1055,4 +1022,5 @@ int BKE_icon_ensure_studio_light(struct StudioLight *sl, int id_type) icon->id_type = id_type; return icon_id; } + /** \} */ diff --git a/source/blender/blenkernel/intern/idprop.c b/source/blender/blenkernel/intern/idprop.c index f7411f541b7..bb6458331da 100644 --- a/source/blender/blenkernel/intern/idprop.c +++ b/source/blender/blenkernel/intern/idprop.c @@ -76,10 +76,6 @@ static size_t idp_size_table[] = { /* --------- property array type -------------*/ -/** - * \note as a start to move away from the stupid IDP_New function, this type - * has its own allocation function. - */ IDProperty *IDP_NewIDPArray(const char *name) { IDProperty *prop = MEM_callocN(sizeof(IDProperty), "IDProperty prop array"); @@ -127,7 +123,6 @@ static void IDP_FreeIDPArray(IDProperty *prop, const bool do_id_user) } } -/* shallow copies item */ void IDP_SetIndexArray(IDProperty *prop, int index, IDProperty *item) { BLI_assert(prop->type == IDP_IDPARRAY); @@ -229,7 +224,6 @@ static void idp_resize_group_array(IDProperty *prop, int newlen, void *newarr) } } -/* This function works for strings too! */ void IDP_ResizeArray(IDProperty *prop, int newlen) { const bool is_grow = newlen >= prop->len; @@ -351,19 +345,13 @@ static IDProperty *IDP_CopyArray(const IDProperty *prop, const int flag) return newp; } + /** \} */ /* -------------------------------------------------------------------- */ /** \name String Functions (IDProperty String API) * \{ */ -/** - * - * \param st: The string to assign. - * \param name: The property name. - * \param maxlen: The size of the new string (including the \0 terminator). - * \return The new string property. - */ IDProperty *IDP_NewString(const char *st, const char *name, int maxlen) { IDProperty *prop = MEM_callocN(sizeof(IDProperty), "IDProperty string"); @@ -457,6 +445,7 @@ void IDP_FreeString(IDProperty *prop) MEM_freeN(prop->data.pointer); } } + /** \} */ /* -------------------------------------------------------------------- */ @@ -514,8 +503,6 @@ static IDProperty *IDP_CopyGroup(const IDProperty *prop, const int flag) return newp; } -/* use for syncing proxies. - * When values name and types match, copy the values, else ignore */ void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) { BLI_assert(dest->type == IDP_GROUP); @@ -565,9 +552,6 @@ void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_a } } -/** - * Replaces all properties with the same name in a destination group from a source group. - */ void IDP_ReplaceGroupInGroup(IDProperty *dest, const IDProperty *src) { BLI_assert(dest->type == IDP_GROUP); @@ -592,10 +576,6 @@ void IDP_ReplaceGroupInGroup(IDProperty *dest, const IDProperty *src) } } -/** - * Checks if a property with the same name as prop exists, and if so replaces it. - * Use this to preserve order! - */ void IDP_ReplaceInGroup_ex(IDProperty *group, IDProperty *prop, IDProperty *prop_exist) { BLI_assert(group->type == IDP_GROUP); @@ -618,10 +598,6 @@ void IDP_ReplaceInGroup(IDProperty *group, IDProperty *prop) IDP_ReplaceInGroup_ex(group, prop, prop_exist); } -/** - * If a property is missing in \a dest, add it. - * Do it recursively. - */ void IDP_MergeGroup_ex(IDProperty *dest, const IDProperty *src, const bool do_overwrite, @@ -663,25 +639,11 @@ void IDP_MergeGroup_ex(IDProperty *dest, } } -/** - * If a property is missing in \a dest, add it. - * Do it recursively. - */ void IDP_MergeGroup(IDProperty *dest, const IDProperty *src, const bool do_overwrite) { IDP_MergeGroup_ex(dest, src, do_overwrite, 0); } -/** - * This function has a sanity check to make sure ID properties with the same name don't - * get added to the group. - * - * The sanity check just means the property is not added to the group if another property - * exists with the same name; the client code using ID properties then needs to detect this - * (the function that adds new properties to groups, #IDP_AddToGroup, - * returns false if a property can't be added to the group, and true if it can) - * and free the property. - */ bool IDP_AddToGroup(IDProperty *group, IDProperty *prop) { BLI_assert(group->type == IDP_GROUP); @@ -695,10 +657,6 @@ bool IDP_AddToGroup(IDProperty *group, IDProperty *prop) return false; } -/** - * This is the same as IDP_AddToGroup, only you pass an item - * in the group list to be inserted after. - */ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew) { BLI_assert(group->type == IDP_GROUP); @@ -712,12 +670,6 @@ bool IDP_InsertToGroup(IDProperty *group, IDProperty *previous, IDProperty *pnew return false; } -/** - * \note this does not free the property!! - * - * To free the property, you have to do: - * IDP_FreeProperty(prop); - */ void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) { BLI_assert(group->type == IDP_GROUP); @@ -727,9 +679,6 @@ void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) BLI_remlink(&group->data.group, prop); } -/** - * Removes the property from the group and frees it. - */ void IDP_FreeFromGroup(IDProperty *group, IDProperty *prop) { IDP_RemoveFromGroup(group, prop); @@ -742,7 +691,6 @@ IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop, const char *name) return (IDProperty *)BLI_findstring(&prop->data.group, name, offsetof(IDProperty, name)); } -/** same as above but ensure type match */ IDProperty *IDP_GetPropertyTypeFromGroup(const IDProperty *prop, const char *name, const char type) { IDProperty *idprop = IDP_GetPropertyFromGroup(prop, name); @@ -762,16 +710,13 @@ static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user) } BLI_freelistN(&prop->data.group); } + /** \} */ /* -------------------------------------------------------------------- */ /** \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) { @@ -786,10 +731,6 @@ int IDP_coerce_to_int_or_zero(const IDProperty *prop) } } -/** - * 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) { @@ -804,10 +745,6 @@ double IDP_coerce_to_double_or_zero(const IDProperty *prop) } } -/** - * 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) { @@ -845,10 +782,6 @@ IDProperty *IDP_CopyProperty(const IDProperty *prop) return IDP_CopyProperty_ex(prop, 0); } -/** - * Copy content from source IDProperty into destination one, freeing destination property's content - * first. - */ void IDP_CopyPropertyContent(IDProperty *dst, IDProperty *src) { IDProperty *idprop_tmp = IDP_CopyProperty(src); @@ -858,11 +791,6 @@ void IDP_CopyPropertyContent(IDProperty *dst, IDProperty *src) IDP_FreeProperty(idprop_tmp); } -/** - * Get the Group property that contains the id properties for ID id. Set create_if_needed - * to create the Group property and attach it to id if it doesn't exist; otherwise - * the function will return NULL if there's no Group property attached to the ID. - */ IDProperty *IDP_GetProperties(ID *id, const bool create_if_needed) { if (id->properties) { @@ -880,8 +808,6 @@ IDProperty *IDP_GetProperties(ID *id, const bool create_if_needed) return id->properties; } -/** - * \param is_strict: When false treat missing items as a match */ bool IDP_EqualsProperties_ex(IDProperty *prop1, IDProperty *prop2, const bool is_strict) { if (prop1 == NULL && prop2 == NULL) { @@ -974,33 +900,6 @@ bool IDP_EqualsProperties(IDProperty *prop1, IDProperty *prop2) return IDP_EqualsProperties_ex(prop1, prop2, true); } -/** - * Allocate a new ID. - * - * This function takes three arguments: the ID property type, a union which defines - * its initial value, and a name. - * - * The union is simple to use; see the top of BKE_idprop.h for its definition. - * An example of using this function: - * - * \code{.c} - * IDPropertyTemplate val; - * IDProperty *group, *idgroup, *color; - * group = IDP_New(IDP_GROUP, val, "group1"); // groups don't need a template. - * - * val.array.len = 4 - * val.array.type = IDP_FLOAT; - * color = IDP_New(IDP_ARRAY, val, "color1"); - * - * idgroup = IDP_GetProperties(some_id, 1); - * IDP_AddToGroup(idgroup, color); - * IDP_AddToGroup(idgroup, group); - * \endcode - * - * Note that you MUST either attach the id property to an id property group with - * IDP_AddToGroup or MEM_freeN the property, doing anything else might result in - * a memory leak. - */ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char *name) { IDProperty *prop = NULL; @@ -1096,11 +995,6 @@ IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const char * return prop; } -/** - * 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) @@ -1175,10 +1069,6 @@ void IDP_ui_data_free(IDProperty *prop) 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. - */ void IDP_FreePropertyContent_ex(IDProperty *prop, const bool do_id_user) { switch (prop->type) { @@ -1241,14 +1131,6 @@ void IDP_Reset(IDProperty *prop, const IDProperty *reference) } } -/** - * Loop through all ID properties in hierarchy of given \a id_property_root included. - * - * \note Container types (groups and arrays) are processed after applying the callback on them. - * - * \param type_filter: If not 0, only apply callback on properties of matching types, see - * IDP_TYPE_FILTER_ enum in DNA_ID.h. - */ void IDP_foreach_property(IDProperty *id_property_root, const int type_filter, IDPForeachPropertyCallback callback, diff --git a/source/blender/blenkernel/intern/idprop_create.cc b/source/blender/blenkernel/intern/idprop_create.cc new file mode 100644 index 00000000000..12f2fdc6a63 --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_create.cc @@ -0,0 +1,140 @@ +/* + * 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 by Blender Foundation. + */ + +#include <type_traits> + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +namespace blender::bke::idprop { + +/* -------------------------------------------------------------------- */ +/** \name Create Functions + * \{ */ + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, int32_t value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.i = value; + IDProperty *property = IDP_New(IDP_INT, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, float value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.f = value; + IDProperty *property = IDP_New(IDP_FLOAT, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, double value) +{ + IDPropertyTemplate prop_template{0}; + prop_template.d = value; + IDProperty *property = IDP_New(IDP_DOUBLE, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + const StringRefNull value) +{ + IDProperty *property = IDP_NewString(value.c_str(), prop_name.c_str(), value.size() + 1); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +static std::unique_ptr<IDProperty, IDPropertyDeleter> array_create(const StringRefNull prop_name, + eIDPropertyType subtype, + size_t array_len) +{ + IDPropertyTemplate prop_template{0}; + prop_template.array.len = array_len; + prop_template.array.type = subtype; + IDProperty *property = IDP_New(IDP_ARRAY, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +static void array_values_set(IDProperty *property, + const void *values, + size_t values_len, + size_t value_size) +{ + BLI_assert(values); + BLI_assert(property->len == values_len); + memcpy(IDP_Array(property), values, values_len * value_size); +} + +/** + * Create a IDProperty array of `id_property_subtype` and fill it with the given values. + */ +template< + /** C-Primitive type of the array. Can be int32_t, float, double. */ + typename PrimitiveType, + /** Subtype of the ID_ARRAY. Must match PrimitiveType. */ + eIDPropertyType id_property_subtype> +std::unique_ptr<IDProperty, IDPropertyDeleter> create_array(StringRefNull prop_name, + Span<PrimitiveType> values) +{ + static_assert(std::is_same_v<PrimitiveType, int32_t> || std::is_same_v<PrimitiveType, float_t> || + std::is_same_v<PrimitiveType, double>, + "Allowed values for PrimitiveType are int32_t, float and double."); + static_assert(!std::is_same_v<PrimitiveType, int32_t> || id_property_subtype == IDP_INT, + "PrimitiveType and id_property_type do not match (int32_t)."); + static_assert(!std::is_same_v<PrimitiveType, float> || id_property_subtype == IDP_FLOAT, + "PrimitiveType and id_property_type do not match (float)."); + static_assert(!std::is_same_v<PrimitiveType, double> || id_property_subtype == IDP_DOUBLE, + "PrimitiveType and id_property_type do not match (double)."); + + const int64_t values_len = values.size(); + BLI_assert(values_len > 0); + std::unique_ptr<IDProperty, IDPropertyDeleter> property = array_create( + prop_name.c_str(), id_property_subtype, values_len); + array_values_set( + property.get(), static_cast<const void *>(values.data()), values_len, sizeof(PrimitiveType)); + return property; +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<int32_t> values) +{ + return create_array<int32_t, IDP_INT>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<float> values) +{ + return create_array<float, IDP_FLOAT>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create(const StringRefNull prop_name, + Span<double> values) +{ + return create_array<double, IDP_DOUBLE>(prop_name, values); +} + +std::unique_ptr<IDProperty, IDPropertyDeleter> create_group(const StringRefNull prop_name) +{ + IDPropertyTemplate prop_template{0}; + IDProperty *property = IDP_New(IDP_GROUP, &prop_template, prop_name.c_str()); + return std::unique_ptr<IDProperty, IDPropertyDeleter>(property); +} + +/* \} */ + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/intern/idprop_serialize.cc b/source/blender/blenkernel/intern/idprop_serialize.cc new file mode 100644 index 00000000000..08a7f13b806 --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_serialize.cc @@ -0,0 +1,844 @@ +/* + * 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 by Blender Foundation. + */ + +#include <optional> + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +#include "BLI_listbase.h" + +namespace blender::bke::idprop { +using namespace blender::io::serialize; + +/* Forward declarations */ +class IDPropertySerializer; +struct DictionaryEntryParser; +static IDProperty *idprop_from_value(const DictionaryValue &value); +static const IDPropertySerializer &serializer_for(eIDPropertyType property_type); +static const IDPropertySerializer &serializer_for(StringRef idprop_typename); + +/* -------------------------------------------------------------------- */ +/** \name ID property serialization. + * \{ */ + +/* Definitions */ +static constexpr StringRef IDP_KEY_NAME("name"); +static constexpr StringRef IDP_KEY_TYPE("type"); +static constexpr StringRef IDP_KEY_SUBTYPE("subtype"); +static constexpr StringRef IDP_KEY_VALUE("value"); + +static constexpr StringRef IDP_PROPERTY_TYPENAME_STRING("IDP_STRING"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_INT("IDP_INT"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_FLOAT("IDP_FLOAT"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_DOUBLE("IDP_DOUBLE"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_ARRAY("IDP_ARRAY"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_GROUP("IDP_GROUP"); +static constexpr StringRef IDP_PROPERTY_TYPENAME_UNKNOWN("IDP_UNKNOWN"); + +/** + * \brief Base class for (de)serializing IDProperties. + * + * Has a subclass for supported IDProperties and one for unsupported IDProperties. + */ +class IDPropertySerializer { + public: + constexpr IDPropertySerializer() = default; + + /** + * \brief return the type name for (de)serializing. + * Type name is stored in the `type` or `subtype` attribute of the serialized id_property. + */ + virtual std::string type_name() const = 0; + + /** + * \brief return the IDPropertyType for (de)serializing. + */ + virtual std::optional<eIDPropertyType> property_type() const = 0; + + /** + * \brief create dictionary containing the given id_property. + */ + virtual std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const = 0; + + /** + * \brief convert the entry to an id property. + */ + virtual std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const = 0; + + /** + * \brief Can the serializer be used? + * + * IDP_ID and IDP_IDPARRAY aren't supported for serialization. + */ + virtual bool supports_serializing() const + { + return true; + } + + protected: + /** + * \brief Create a new DictionaryValue instance. + * + * Only fill the dictionary with common attributes (name, type). + */ + std::shared_ptr<DictionaryValue> create_dictionary(const struct IDProperty *id_property) const + { + std::shared_ptr<DictionaryValue> result = std::make_shared<DictionaryValue>(); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_NAME, new StringValue(id_property->name))); + attributes.append_as(std::pair(IDP_KEY_TYPE, new StringValue(type_name()))); + return result; + } +}; + +/** + * \brief Helper class for parsing DictionaryValues. + */ +struct DictionaryEntryParser { + const DictionaryValue::Lookup lookup; + + public: + explicit DictionaryEntryParser(const DictionaryValue &value) : lookup(value.create_lookup()) + { + } + + std::optional<eIDPropertyType> get_type() const + { + return get_id_property_type(IDP_KEY_TYPE); + } + + std::optional<eIDPropertyType> get_subtype() const + { + return get_id_property_type(IDP_KEY_SUBTYPE); + } + + std::optional<std::string> get_name() const + { + return get_string(IDP_KEY_NAME); + } + + std::optional<std::string> get_string_value() const + { + return get_string(IDP_KEY_VALUE); + } + + std::optional<int32_t> get_int_value() const + { + return get_int(IDP_KEY_VALUE); + } + + std::optional<float> get_float_value() const + { + return get_float(IDP_KEY_VALUE); + } + + std::optional<double> get_double_value() const + { + return get_double(IDP_KEY_VALUE); + } + + const ArrayValue *get_array_value() const + { + return get_array(IDP_KEY_VALUE); + } + + std::optional<Vector<int32_t>> get_array_int_value() const + { + return get_array_primitive<int32_t, IntValue>(IDP_KEY_VALUE); + } + + std::optional<Vector<float>> get_array_float_value() const + { + return get_array_primitive<float, DoubleValue>(IDP_KEY_VALUE); + } + + std::optional<Vector<double>> get_array_double_value() const + { + return get_array_primitive<double, DoubleValue>(IDP_KEY_VALUE); + } + + private: + std::optional<std::string> get_string(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::String) { + return std::nullopt; + } + + return value->as_string_value()->value(); + } + + const ArrayValue *get_array(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return nullptr; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Array) { + return nullptr; + } + + return value->as_array_value(); + } + + std::optional<int32_t> get_int(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Int) { + return std::nullopt; + } + + return value->as_int_value()->value(); + } + + std::optional<double> get_double(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Double) { + return std::nullopt; + } + + return value->as_double_value()->value(); + } + + std::optional<float> get_float(StringRef key) const + { + return static_cast<std::optional<float>>(get_double(key)); + } + + template<typename PrimitiveType, typename ValueType> + std::optional<Vector<PrimitiveType>> get_array_primitive(StringRef key) const + { + const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key); + if (value_ptr == nullptr) { + return std::nullopt; + } + const DictionaryValue::LookupValue &value = *value_ptr; + + if (value->type() != eValueType::Array) { + return std::nullopt; + } + + Vector<PrimitiveType> result; + const ArrayValue::Items &elements = value->as_array_value()->elements(); + for (const ArrayValue::Item &element : elements) { + const ValueType *value_type = static_cast<const ValueType *>(element.get()); + PrimitiveType primitive_value = value_type->value(); + result.append_as(primitive_value); + } + + return result; + } + + std::optional<eIDPropertyType> get_id_property_type(StringRef key) const + { + std::optional<std::string> string_value = get_string(key); + if (!string_value.has_value()) { + return std::nullopt; + } + const IDPropertySerializer &serializer = serializer_for(*string_value); + return serializer.property_type(); + } +}; + +/** \brief IDPSerializer for IDP_STRING. */ +class IDPStringSerializer : public IDPropertySerializer { + public: + constexpr IDPStringSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_STRING; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_STRING; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new StringValue(IDP_String(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_STRING); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<std::string> string_value = entry_reader.get_string_value(); + if (!string_value.has_value()) { + return nullptr; + } + return create(name->c_str(), string_value->c_str()); + } +}; + +/** \brief IDPSerializer for IDP_INT. */ +class IDPIntSerializer : public IDPropertySerializer { + public: + constexpr IDPIntSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_INT; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_INT; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new IntValue(IDP_Int(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_INT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<int32_t> extracted_value = entry_reader.get_int_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_FLOAT. */ +class IDPFloatSerializer : public IDPropertySerializer { + public: + constexpr IDPFloatSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_FLOAT; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_FLOAT; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new DoubleValue(IDP_Float(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_FLOAT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<float> extracted_value = entry_reader.get_float_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_DOUBLE. */ +class IDPDoubleSerializer : public IDPropertySerializer { + public: + constexpr IDPDoubleSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_DOUBLE; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_DOUBLE; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + attributes.append_as(std::pair(IDP_KEY_VALUE, new DoubleValue(IDP_Double(id_property)))); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_DOUBLE); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<double> extracted_value = entry_reader.get_double_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_ARRAY. */ +class IDPArraySerializer : public IDPropertySerializer { + public: + constexpr IDPArraySerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_ARRAY; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_ARRAY; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + const IDPropertySerializer &subtype_serializer = serializer_for( + static_cast<eIDPropertyType>(id_property->subtype)); + attributes.append_as( + std::pair(IDP_KEY_SUBTYPE, new StringValue(subtype_serializer.type_name()))); + + std::shared_ptr<ArrayValue> array = std::make_shared<ArrayValue>(); + switch (static_cast<eIDPropertyType>(id_property->subtype)) { + case IDP_INT: { + int32_t *values = static_cast<int32_t *>(IDP_Array(id_property)); + add_values<int32_t, IntValue>(array.get(), Span<int32_t>(values, id_property->len)); + break; + } + + case IDP_FLOAT: { + float *values = static_cast<float *>(IDP_Array(id_property)); + add_values<float, DoubleValue>(array.get(), Span<float>(values, id_property->len)); + break; + } + + case IDP_DOUBLE: { + double *values = static_cast<double *>(IDP_Array(id_property)); + add_values<double, DoubleValue>(array.get(), Span<double>(values, id_property->len)); + break; + } + + case IDP_GROUP: { + IDProperty *values = static_cast<IDProperty *>(IDP_Array(id_property)); + add_values(array.get(), Span<IDProperty>(values, id_property->len)); + break; + } + + default: { + /* IDP_ARRAY only supports IDP_INT, IDP_FLOAT, IDP_DOUBLE and IDP_GROUP. */ + BLI_assert_unreachable(); + break; + } + } + attributes.append_as(std::pair(IDP_KEY_VALUE, std::move(array))); + + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + std::optional<eIDPropertyType> property_subtype = entry_reader.get_subtype(); + if (!property_subtype.has_value()) { + return nullptr; + } + + switch (*property_subtype) { + case IDP_INT: + return idprop_array_int_from_value(entry_reader); + + case IDP_FLOAT: + return idprop_array_float_from_value(entry_reader); + + case IDP_DOUBLE: + return idprop_array_double_from_value(entry_reader); + + default: + break; + } + return nullptr; + } + + private: + /** Add the given values to array. */ + template</* C-primitive type of the values to add. Possible types are `float`, `int32_t` or + * `double`. */ + typename PrimitiveType, + /* Type of value that can store the PrimitiveType in the Array. */ + typename ValueType> + void add_values(ArrayValue *array, Span<PrimitiveType> values) const + { + ArrayValue::Items &items = array->elements(); + for (PrimitiveType value : values) { + items.append_as(std::make_shared<ValueType>(value)); + } + } + + void add_values(ArrayValue *array, Span<IDProperty> values) const + { + ArrayValue::Items &items = array->elements(); + for (const IDProperty &id_property : values) { + const IDPropertySerializer &value_serializer = serializer_for( + static_cast<eIDPropertyType>(id_property.type)); + if (!value_serializer.supports_serializing()) { + continue; + } + std::shared_ptr<DictionaryValue> value = value_serializer.idprop_to_dictionary(&id_property); + items.append_as(value); + } + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_int_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_INT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<int32_t>> extracted_value = entry_reader.get_array_int_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_float_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_FLOAT); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<float>> extracted_value = entry_reader.get_array_float_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> idprop_array_double_from_value( + DictionaryEntryParser &entry_reader) const + { + BLI_assert(*(entry_reader.get_type()) == IDP_ARRAY); + BLI_assert(*(entry_reader.get_subtype()) == IDP_DOUBLE); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + std::optional<Vector<double>> extracted_value = entry_reader.get_array_double_value(); + if (!extracted_value.has_value()) { + return nullptr; + } + return create(name->c_str(), *extracted_value); + } +}; + +/** \brief IDPSerializer for IDP_GROUP. */ +class IDPGroupSerializer : public IDPropertySerializer { + public: + constexpr IDPGroupSerializer() = default; + + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_GROUP; + } + + std::optional<eIDPropertyType> property_type() const override + { + return IDP_GROUP; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *id_property) const override + { + std::shared_ptr<DictionaryValue> result = create_dictionary(id_property); + DictionaryValue::Items &attributes = result->elements(); + std::shared_ptr<ArrayValue> array = std::make_shared<ArrayValue>(); + ArrayValue::Items &elements = array->elements(); + + LISTBASE_FOREACH (IDProperty *, sub_property, &id_property->data.group) { + + const IDPropertySerializer &sub_property_serializer = serializer_for( + static_cast<eIDPropertyType>(sub_property->type)); + elements.append_as(sub_property_serializer.idprop_to_dictionary(sub_property)); + } + + attributes.append_as(std::pair(IDP_KEY_VALUE, array)); + return result; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &entry_reader) const override + { + BLI_assert(*(entry_reader.get_type()) == IDP_GROUP); + std::optional<std::string> name = entry_reader.get_name(); + if (!name.has_value()) { + return nullptr; + } + + const ArrayValue *array = entry_reader.get_array_value(); + if (array == nullptr) { + return nullptr; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> result = create_group(name->c_str()); + for (const ArrayValue::Item &element : array->elements()) { + if (element->type() != eValueType::Dictionary) { + continue; + } + const DictionaryValue *subobject = element->as_dictionary_value(); + IDProperty *subproperty = idprop_from_value(*subobject); + IDP_AddToGroup(result.get(), subproperty); + } + + return result; + } +}; + +/** + * \brief Dummy serializer for unknown and unsupported types. + */ +class IDPUnknownSerializer : public IDPropertySerializer { + public: + constexpr IDPUnknownSerializer() = default; + std::string type_name() const override + { + return IDP_PROPERTY_TYPENAME_UNKNOWN; + } + std::optional<eIDPropertyType> property_type() const override + { + return std::nullopt; + } + + std::shared_ptr<DictionaryValue> idprop_to_dictionary( + const struct IDProperty *UNUSED(id_property)) const override + { + BLI_assert_unreachable(); + return nullptr; + } + + bool supports_serializing() const override + { + return false; + } + + std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop( + DictionaryEntryParser &UNUSED(entry_reader)) const override + { + return nullptr; + } +}; + +/* Serializers are constructed statically to remove construction/destruction. */ +static constexpr IDPStringSerializer IDP_SERIALIZER_STRING; +static constexpr IDPIntSerializer IDP_SERIALIZER_INT; +static constexpr IDPFloatSerializer IDP_SERIALIZER_FLOAT; +static constexpr IDPDoubleSerializer IDP_SERIALIZER_DOUBLE; +static constexpr IDPArraySerializer IDP_SERIALIZER_ARRAY; +static constexpr IDPGroupSerializer IDP_SERIALIZER_GROUP; +static constexpr IDPUnknownSerializer IDP_SERIALIZER_UNKNOWN; + +/** \brief get the serializer for the given property type. */ +static const IDPropertySerializer &serializer_for(eIDPropertyType property_type) +{ + switch (property_type) { + case IDP_STRING: + return IDP_SERIALIZER_STRING; + + case IDP_INT: + return IDP_SERIALIZER_INT; + + case IDP_FLOAT: + return IDP_SERIALIZER_FLOAT; + + case IDP_DOUBLE: + return IDP_SERIALIZER_DOUBLE; + + case IDP_ARRAY: + return IDP_SERIALIZER_ARRAY; + + case IDP_GROUP: + return IDP_SERIALIZER_GROUP; + + default: + BLI_assert_msg(false, "Trying to convert an unsupported/unknown property type to a string"); + return IDP_SERIALIZER_UNKNOWN; + } +} + +/** \brief get serializer for the given typename. */ +static const IDPropertySerializer &serializer_for(StringRef idprop_typename) +{ + if (idprop_typename == IDP_PROPERTY_TYPENAME_STRING) { + return IDP_SERIALIZER_STRING; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_INT) { + return IDP_SERIALIZER_INT; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_FLOAT) { + return IDP_SERIALIZER_FLOAT; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_DOUBLE) { + return IDP_SERIALIZER_DOUBLE; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_ARRAY) { + return IDP_SERIALIZER_ARRAY; + } + if (idprop_typename == IDP_PROPERTY_TYPENAME_GROUP) { + return IDP_SERIALIZER_GROUP; + } + return IDP_SERIALIZER_UNKNOWN; +} + +/* \} */ + +/* -------------------------------------------------------------------- */ +/** \name IDProperty to Value + * \{ */ +std::unique_ptr<ArrayValue> convert_to_serialize_values(const struct IDProperty *properties) +{ + BLI_assert(properties != nullptr); + std::unique_ptr<ArrayValue> result = std::make_unique<ArrayValue>(); + ArrayValue::Items &elements = result->elements(); + const struct IDProperty *current_property = properties; + while (current_property != nullptr) { + const IDPropertySerializer &serializer = serializer_for( + static_cast<eIDPropertyType>(current_property->type)); + if (serializer.supports_serializing()) { + elements.append_as(serializer.idprop_to_dictionary(current_property)); + } + current_property = current_property->next; + } + + return result; +} + +/* \} */ + +/* -------------------------------------------------------------------- */ +/** \name IDProperty from Value + * \{ */ + +static IDProperty *idprop_from_value(const DictionaryValue &value) +{ + DictionaryEntryParser entry_reader(value); + std::optional<eIDPropertyType> property_type = entry_reader.get_type(); + if (!property_type.has_value()) { + return nullptr; + } + + const IDPropertySerializer &serializer = serializer_for(*property_type); + return serializer.entry_to_idprop(entry_reader).release(); +} + +static IDProperty *idprop_from_value(const ArrayValue &value) +{ + IDProperty *result = nullptr; + IDProperty *previous_added = nullptr; + + const ArrayValue::Items &elements = value.elements(); + for (const ArrayValue::Item &element : elements) { + if (element->type() != eValueType::Dictionary) { + continue; + } + const DictionaryValue *object_value = element->as_dictionary_value(); + IDProperty *last_created = idprop_from_value(*object_value); + if (last_created == nullptr) { + continue; + } + + if (result == nullptr) { + result = last_created; + } + + if (previous_added) { + previous_added->next = last_created; + } + last_created->prev = previous_added; + previous_added = last_created; + } + + return result; +} + +IDProperty *convert_from_serialize_value(const Value &value) +{ + if (value.type() != eValueType::Array) { + return nullptr; + } + + return idprop_from_value(*value.as_array_value()); +} + +/* \} */ + +} // namespace blender::bke::idprop diff --git a/source/blender/blenkernel/intern/idprop_serialize_test.cc b/source/blender/blenkernel/intern/idprop_serialize_test.cc new file mode 100644 index 00000000000..eeee3fc2aea --- /dev/null +++ b/source/blender/blenkernel/intern/idprop_serialize_test.cc @@ -0,0 +1,448 @@ +/* + * 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 by Blender Foundation. + */ + +#include "testing/testing.h" + +#include "DNA_ID.h" + +#include "BKE_idprop.hh" + +namespace blender::bke::idprop::tests { + +using namespace blender::io::serialize; + +static void check_container_value(ArrayValue *value) +{ + ASSERT_NE(value, nullptr); + ASSERT_EQ(value->type(), eValueType::Array); + const ArrayValue::Items elements = value->elements(); + EXPECT_FALSE(elements.is_empty()); + EXPECT_EQ(elements.size(), 1); + + const ArrayValue::Item &item = value->elements()[0]; + ASSERT_EQ(item->type(), eValueType::Dictionary); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const std::string expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::String); + EXPECT_EQ(element->as_string_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const int32_t expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Int); + EXPECT_EQ(element->as_int_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const float expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Double); + EXPECT_EQ(element->as_double_value()->value(), expected_value); +} + +static void check_object_attribute(const DictionaryValue::Lookup &lookup, + const std::string expected_key, + const double expected_value) +{ + EXPECT_TRUE(lookup.contains(expected_key)); + const std::shared_ptr<Value> &element = *lookup.lookup_ptr(expected_key); + ASSERT_EQ(element->type(), eValueType::Double); + EXPECT_EQ(element->as_double_value()->value(), expected_value); +} + +static void test_string_to_value(const StringRefNull prop_name, const StringRefNull prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_STRING"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_string_to_value) +{ + test_string_to_value("mykey", "mycontent"); +} + +static void test_int_to_value(const StringRefNull prop_name, int32_t prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_INT"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_int_to_value) +{ + test_int_to_value("mykey", 0); +} + +static void test_float_to_value(const StringRefNull prop_name, float prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_FLOAT"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_float_to_value) +{ + test_float_to_value("mykey", 0.2f); +} + +static void test_double_to_value(const StringRefNull prop_name, double prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 3); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_DOUBLE"); + check_object_attribute(lookup, "value", prop_content); +} + +TEST(idprop, convert_idp_double_to_value) +{ + test_double_to_value("mykey", 0.2); +} + +template<typename PrimitiveType, typename ValueType> +static void test_array_to_value(const StringRefNull prop_name, Vector<PrimitiveType> prop_content) +{ + std::unique_ptr<IDProperty, IDPropertyDeleter> property = create(prop_name, prop_content); + std::unique_ptr<ArrayValue> value = convert_to_serialize_values(property.get()); + + check_container_value(value.get()); + const ArrayValue::Item &item = value->elements()[0]; + const DictionaryValue *object = item->as_dictionary_value(); + const DictionaryValue::Lookup lookup = object->create_lookup(); + + EXPECT_EQ(lookup.size(), 4); + check_object_attribute(lookup, "name", prop_name); + check_object_attribute(lookup, "type", "IDP_ARRAY"); + + const std::shared_ptr<Value> &element = *lookup.lookup_ptr("value"); + const ArrayValue *subvalues = element->as_array_value(); + ASSERT_NE(subvalues, nullptr); + const ArrayValue::Items &subitems = subvalues->elements(); + ASSERT_EQ(subitems.size(), prop_content.size()); + + for (size_t i = 0; i < prop_content.size(); i++) { + EXPECT_EQ(static_cast<ValueType *>(subitems[i].get())->value(), prop_content[i]); + } +} + +TEST(idprop, convert_idp_int_array_to_value) +{ + test_array_to_value<int32_t, IntValue>("my_integer_array", + {-16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16}); +} + +TEST(idprop, convert_idp_float_array_to_value) +{ + test_array_to_value<float, DoubleValue>( + "my_float_array", {-16.8f, -8.4f, -4.2f, -2.1f, -1.0f, 0.0f, 1.0f, 2.1f, 4.2f, 8.4f, 16.8f}); +} + +TEST(idprop, convert_idp_double_array_to_value) +{ + test_array_to_value<double, DoubleValue>( + "my_double_array", {-16.8, -8.4, -4.2, -2.1, -1.0, 0.0, 1.0, 2.1, 4.2, 8.4, 16.8}); +} + +static std::unique_ptr<Value> parse_json(StringRef input) +{ + std::stringstream is(input); + JsonFormatter json; + std::unique_ptr<Value> value = json.deserialize(is); + return value; +} + +static std::string to_json(const Value &value) +{ + std::stringstream out; + JsonFormatter json; + json.serialize(out, value); + return out.str(); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + StringRef expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_STRING); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_String(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + int32_t expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_INT); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Int(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + float expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_FLOAT); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Float(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + double expected_value) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_DOUBLE); + EXPECT_EQ(id_property->name, expected_name); + EXPECT_EQ(IDP_Double(id_property), expected_value); +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<int32_t> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_INT); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + int32_t *idprop_values = static_cast<int32_t *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<float> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_FLOAT); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + float *idprop_values = static_cast<float *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +static void test_idprop(const IDProperty *id_property, + StringRef expected_name, + const Vector<double> &values) +{ + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_ARRAY); + EXPECT_EQ(id_property->subtype, IDP_DOUBLE); + EXPECT_EQ(id_property->len, values.size()); + EXPECT_EQ(id_property->name, expected_name); + double *idprop_values = static_cast<double *>(IDP_Array(id_property)); + for (int i = 0; i < values.size(); i++) { + EXPECT_EQ(idprop_values[i], values[i]); + } +} + +template<typename Type> +static void test_convert_idprop_from_value(StringRef input, + StringRef expected_name, + Type expected_value) +{ + std::unique_ptr<Value> value = parse_json(input); + IDProperty *id_property = convert_from_serialize_value(*value); + test_idprop(id_property, expected_name, expected_value); + IDP_FreeProperty(id_property); +} + +TEST(idprop, convert_idp_string_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyStringName","type":"IDP_STRING","value":"MyString"}])", + "MyStringName", + "MyString"); +} + +TEST(idprop, convert_idp_int_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42}])", "MyIntegerName", 42); +} + +TEST(idprop, convert_idp_float_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyFloatName","type":"IDP_FLOAT","value":42.24}])", "MyFloatName", 42.24f); +} + +TEST(idprop, convert_idp_double_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])", "MyDoubleName", 42.24); +} + +TEST(idprop, convert_idp_array_int_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_INT","value":[42, 24, 35]}])", + "MyArrayName", + Vector<int32_t>{42, 24, 35}); +} + +TEST(idprop, convert_idp_array_float_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_FLOAT","value":[42.0, 24.4, 35.2]}])", + "MyArrayName", + Vector<float>{42.0f, 24.4f, 35.2f}); +} + +TEST(idprop, convert_idp_array_double_from_value) +{ + test_convert_idprop_from_value( + R"([{"name":"MyArrayName","type":"IDP_ARRAY","subtype":"IDP_DOUBLE","value":[42.43,24.5,35.8]}])", + "MyArrayName", + Vector<double>{42.43, 24.5, 35.8}); +} + +TEST(idprop, convert_idp_multiple_from_value) +{ + static const std::string input_json = + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42},{"name":"MyStringName","type":"IDP_STRING","value":"MyString"},{"name":"MyFloatName","type":"IDP_FLOAT","value":42.24},{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + IDProperty *id_property_1 = id_property; + ASSERT_NE(id_property_1, nullptr); + IDProperty *id_property_2 = id_property_1->next; + ASSERT_NE(id_property_2, nullptr); + IDProperty *id_property_3 = id_property_2->next; + ASSERT_NE(id_property_3, nullptr); + IDProperty *id_property_4 = id_property_3->next; + ASSERT_NE(id_property_4, nullptr); + + EXPECT_EQ(id_property_1->prev, nullptr); + EXPECT_EQ(id_property_2->prev, id_property_1); + EXPECT_EQ(id_property_3->prev, id_property_2); + EXPECT_EQ(id_property_4->prev, id_property_3); + EXPECT_EQ(id_property_4->next, nullptr); + + test_idprop(id_property_1, "MyIntegerName", 42); + test_idprop(id_property_2, "MyStringName", "MyString"); + test_idprop(id_property_3, "MyFloatName", 42.24f); + test_idprop(id_property_4, "MyDoubleName", 42.24); + + IDP_FreeProperty(id_property_1); + IDP_FreeProperty(id_property_2); + IDP_FreeProperty(id_property_3); + IDP_FreeProperty(id_property_4); +} + +TEST(idprop, convert_idp_multiple_roundtrip) +{ + static const std::string input_json = + R"([{"name":"MyIntegerName","type":"IDP_INT","value":42},{"name":"MyStringName","type":"IDP_STRING","value":"MyString"},{"name":"MyFloatName","type":"IDP_FLOAT","value":42.2400016784668},{"name":"MyDoubleName","type":"IDP_DOUBLE","value":42.24}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + IDProperty *id_property_1 = id_property; + ASSERT_NE(id_property_1, nullptr); + IDProperty *id_property_2 = id_property_1->next; + ASSERT_NE(id_property_2, nullptr); + IDProperty *id_property_3 = id_property_2->next; + ASSERT_NE(id_property_3, nullptr); + IDProperty *id_property_4 = id_property_3->next; + ASSERT_NE(id_property_4, nullptr); + + std::unique_ptr<Value> value_from_id_properties = convert_to_serialize_values(id_property); + std::string output_json = to_json(*value_from_id_properties); + EXPECT_EQ(input_json, output_json); + + IDP_FreeProperty(id_property_1); + IDP_FreeProperty(id_property_2); + IDP_FreeProperty(id_property_3); + IDP_FreeProperty(id_property_4); +} + +TEST(idprop, convert_idp_group_from_value) +{ + static const std::string input_json = + R"([{"name":"AssetMetaData.properties","type":"IDP_GROUP","value":[{"name":"dimensions","type":"IDP_ARRAY","subtype":"IDP_FLOAT","value":[2.0,2.0,2.0]}]}])"; + std::unique_ptr<Value> value = parse_json(input_json); + + IDProperty *id_property = convert_from_serialize_value(*value); + ASSERT_NE(id_property, nullptr); + EXPECT_EQ(id_property->type, IDP_GROUP); + EXPECT_EQ(BLI_listbase_count(&id_property->data.group), 1); + + test_idprop(static_cast<IDProperty *>(id_property->data.group.first), + "dimensions", + Vector<float>{2.0f, 2.0f, 2.0f}); + + IDP_FreeProperty(id_property); +} + +} // namespace blender::bke::idprop::tests diff --git a/source/blender/blenkernel/intern/idtype.c b/source/blender/blenkernel/intern/idtype.c index d9dc68b1a4f..e6fd6c14d42 100644 --- a/source/blender/blenkernel/intern/idtype.c +++ b/source/blender/blenkernel/intern/idtype.c @@ -158,13 +158,6 @@ static const IDTypeInfo *idtype_get_info_from_name(const char *idtype_name) /* Various helpers/wrappers around #IDTypeInfo structure. */ -/** - * Convert an \a idcode into a name. - * - * \param idcode: The code to convert. - * \return A static string representing the name of - * the code. - */ const char *BKE_idtype_idcode_to_name(const short idcode) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); @@ -172,13 +165,6 @@ const char *BKE_idtype_idcode_to_name(const short idcode) return id_type != NULL ? id_type->name : NULL; } -/** - * Convert an \a idcode into a name (plural). - * - * \param idcode: The code to convert. - * \return A static string representing the name of - * the code. - */ const char *BKE_idtype_idcode_to_name_plural(const short idcode) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); @@ -186,12 +172,6 @@ const char *BKE_idtype_idcode_to_name_plural(const short idcode) return id_type != NULL ? id_type->name_plural : NULL; } -/** - * Convert an \a idcode into its translations' context. - * - * \param idcode: The code to convert. - * \return A static string representing the i18n context of the code. - */ const char *BKE_idtype_idcode_to_translation_context(const short idcode) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); @@ -199,12 +179,6 @@ const char *BKE_idtype_idcode_to_translation_context(const short idcode) return id_type != NULL ? id_type->translation_context : BLT_I18NCONTEXT_DEFAULT; } -/** - * Convert an ID-type name into an \a idcode (ie. #ID_SCE) - * - * \param idtype_name: The ID-type's "user visible name" to convert. - * \return The \a idcode for the name, or 0 if invalid. - */ short BKE_idtype_idcode_from_name(const char *idtype_name) { const IDTypeInfo *id_type = idtype_get_info_from_name(idtype_name); @@ -212,23 +186,11 @@ short BKE_idtype_idcode_from_name(const char *idtype_name) return id_type != NULL ? id_type->id_code : 0; } -/** - * Return if the ID code is a valid ID code. - * - * \param idcode: The code to check. - * \return Boolean, 0 when invalid. - */ bool BKE_idtype_idcode_is_valid(const short idcode) { return BKE_idtype_get_info_from_idcode(idcode) != NULL ? true : false; } -/** - * Check if an ID type is 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) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(idcode); @@ -236,12 +198,6 @@ bool BKE_idtype_idcode_is_linkable(const short idcode) return id_type != NULL ? (id_type->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0 : false; } -/** - * 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); @@ -254,12 +210,6 @@ bool BKE_idtype_idcode_is_only_appendable(const short idcode) 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); @@ -272,9 +222,6 @@ bool BKE_idtype_idcode_append_is_reusable(const short idcode) 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) { #define CASE_IDFILTER(_id) \ @@ -324,9 +271,6 @@ uint64_t BKE_idtype_idcode_to_idfilter(const short idcode) #undef CASE_IDFILTER } -/** - * Convert an \a idfilter into an \a idcode (e.g. #FILTER_ID_OB -> #ID_OB). - */ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) { #define CASE_IDFILTER(_id) \ @@ -375,9 +319,6 @@ short BKE_idtype_idcode_from_idfilter(const uint64_t idfilter) #undef CASE_IDFILTER } -/** - * Convert an \a idcode into an index (e.g. #ID_OB -> #INDEX_ID_OB). - */ int BKE_idtype_idcode_to_index(const short idcode) { #define CASE_IDINDEX(_id) \ @@ -437,9 +378,6 @@ int BKE_idtype_idcode_to_index(const short idcode) #undef CASE_IDINDEX } -/** - * Get an \a idcode from an index (e.g. #INDEX_ID_OB -> #ID_OB). - */ short BKE_idtype_idcode_from_index(const int index) { #define CASE_IDCODE(_id) \ @@ -499,20 +437,11 @@ short BKE_idtype_idcode_from_index(const int index) #undef CASE_IDCODE } -/** - * Return an ID code and steps the index forward 1. - * - * \param index: start as 0. - * \return the code, 0 when all codes have been returned. - */ short BKE_idtype_idcode_iter_step(int *index) { return (*index < ARRAY_SIZE(id_types)) ? BKE_idtype_idcode_from_index((*index)++) : 0; } -/** - * Wrapper around #IDTypeInfo foreach_cache that also handles embedded IDs. - */ void BKE_idtype_id_foreach_cache(struct ID *id, IDTypeForeachCacheFunctionCallback function_callback, void *user_data) diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index b993d743044..c7d58a277e0 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -21,6 +21,7 @@ * \ingroup bke */ +#include <ctype.h> #include <fcntl.h> #include <math.h> #include <stdio.h> @@ -75,6 +76,7 @@ #include "BLT_translation.h" +#include "BKE_bpath.h" #include "BKE_colortools.h" #include "BKE_global.h" #include "BKE_icons.h" @@ -83,6 +85,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_packedFile.h" #include "BKE_report.h" #include "BKE_scene.h" @@ -96,6 +99,7 @@ #include "SEQ_utils.h" /* SEQ_get_topmost_sequence() */ +#include "GPU_material.h" #include "GPU_texture.h" #include "BLI_sys_types.h" /* for intptr_t support */ @@ -112,12 +116,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 data-block 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 data-block 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; @@ -167,6 +185,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) @@ -194,6 +214,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, @@ -211,8 +234,12 @@ static void image_foreach_cache(ID *id, for (int eye = 0; eye < 2; eye++) { for (int a = 0; a < TEXTARGET_COUNT; a++) { 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 = image->gputexture[a][eye]; + key.cache_v = texture; function_callback(id, &key, (void **)&image->gputexture[a][eye][resolution], 0, user_data); } } @@ -229,6 +256,60 @@ static void image_foreach_cache(ID *id, } } +static void image_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Image *ima = (Image *)id; + const eBPathForeachFlag flag = bpath_data->flag; + + if (BKE_image_has_packedfile(ima) && (flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0) { + return; + } + /* Skip empty file paths, these are typically from generated images and + * don't make sense to add directories to until the image has been saved + * once to give it a meaningful value. */ + /* TODO re-assess whether this behavior is desired in the new generic code context. */ + if (!ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED) || + ima->filepath[0] == '\0') { + return; + } + + /* If this is a tiled image, and we're asked to resolve the tokens in the virtual + * filepath, use the first tile to generate a concrete path for use during processing. */ + bool result = false; + if (ima->source == IMA_SRC_TILED && (flag & BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN) != 0) { + char temp_path[FILE_MAX], orig_file[FILE_MAXFILE]; + BLI_strncpy(temp_path, ima->filepath, sizeof(temp_path)); + BLI_split_file_part(temp_path, orig_file, sizeof(orig_file)); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(temp_path, &tile_format); + BKE_image_set_filepath_from_tile_number( + temp_path, udim_pattern, tile_format, ((ImageTile *)ima->tiles.first)->tile_number); + MEM_SAFE_FREE(udim_pattern); + + result = BKE_bpath_foreach_path_fixed_process(bpath_data, temp_path); + if (result) { + /* Put the filepath back together using the new directory and the original file name. */ + char new_dir[FILE_MAXDIR]; + BLI_split_dir_part(temp_path, new_dir, sizeof(new_dir)); + BLI_join_dirfile(ima->filepath, sizeof(ima->filepath), new_dir, orig_file); + } + } + else { + result = BKE_bpath_foreach_path_fixed_process(bpath_data, ima->filepath); + } + + if (result) { + if (flag & BKE_BPATH_FOREACH_PATH_RELOAD_EDITED) { + if (!BKE_image_has_packedfile(ima) && + /* Image may have been painted onto (and not saved, T44543). */ + !BKE_image_is_dirty(ima)) { + BKE_image_signal(bpath_data->bmain, ima, NULL, IMA_SIGNAL_RELOAD); + } + } + } +} + static void image_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Image *ima = (Image *)id; @@ -317,12 +398,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) @@ -346,6 +427,7 @@ IDTypeInfo IDType_ID_IM = { .name_plural = "images", .translation_context = BLT_I18NCONTEXT_ID_IMAGE, .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = image_init_data, .copy_data = image_copy_data, @@ -353,6 +435,7 @@ IDTypeInfo IDType_ID_IM = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = image_foreach_cache, + .foreach_path = image_foreach_path, .owner_get = NULL, .blend_write = image_blend_write, @@ -439,27 +522,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) @@ -505,14 +578,10 @@ static void image_free_anims(Image *ima) } } -/** - * Simply free the image data from memory, - * on display the image can load again (except for render buffers). - */ 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); @@ -525,12 +594,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); } } @@ -539,7 +604,6 @@ void BKE_image_free_buffers(Image *ima) BKE_image_free_buffers_ex(ima, false); } -/** Free (or release) any data used by this image (does not free the image itself). */ void BKE_image_free_data(Image *ima) { image_free_data(&ima->id); @@ -560,7 +624,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); @@ -570,6 +633,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"); } @@ -593,25 +658,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) @@ -643,7 +708,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); @@ -655,15 +722,19 @@ 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. */ bool BKE_image_scale(Image *image, int width, int height) { + /* NOTE: We could be clever and scale all imbuf's + * but since some are mipmaps its not so simple. */ + ImBuf *ibuf; void *lock; @@ -762,9 +833,6 @@ int BKE_image_get_tile_from_pos(struct Image *ima, return tile_number; } -/** - * Return the tile_number for the closest UDIM tile. - */ int BKE_image_find_nearest_tile(const Image *image, const float co[2]) { const float co_floor[2] = {floorf(co[0]), floorf(co[1])}; @@ -847,9 +915,13 @@ Image *BKE_image_load(Main *bmain, const char *filepath) /* exists? */ file = BLI_open(str, O_BINARY | O_RDONLY, 0); if (file == -1) { - return NULL; + if (!BKE_image_tile_filepath_exists(str)) { + return NULL; + } + } + else { + close(file); } - close(file); ima = image_alloc(bmain, BLI_path_basename(filepath), IMA_SRC_FILE, IMA_TYPE_IMAGE); STRNCPY(ima->filepath, filepath); @@ -863,17 +935,13 @@ Image *BKE_image_load(Main *bmain, const char *filepath) return ima; } -/* checks if image was already loaded, then returns same image */ -/* otherwise creates new. */ -/* does not load ibuf itself */ -/* pass on optional frame for #name images */ Image *BKE_image_load_exists_ex(Main *bmain, const char *filepath, bool *r_exists) { Image *ima; char str[FILE_MAX], strtest[FILE_MAX]; STRNCPY(str, filepath); - BLI_path_abs(str, bmain->name); + BLI_path_abs(str, bmain->filepath); /* first search an identical filepath */ for (ima = bmain->images.first; ima; ima = ima->id.next) { @@ -884,11 +952,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; } @@ -1018,7 +1081,6 @@ static ImBuf *add_ibuf_size(unsigned int width, return ibuf; } -/* adds new image block, creates ImBuf and initializes color */ Image *BKE_image_add_generated(Main *bmain, unsigned int width, unsigned int height, @@ -1076,17 +1138,9 @@ 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; } -/** - * Create an image from ibuf. The refcount of ibuf is increased, - * caller should take care to drop its reference by calling - * #IMB_freeImBuf if needed. - */ Image *BKE_image_add_from_imbuf(Main *bmain, ImBuf *ibuf, const char *name) { /* on save, type is changed to FILE in editsima.c */ @@ -1101,8 +1155,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; @@ -1140,7 +1192,6 @@ static bool image_memorypack_imbuf(Image *ima, ImBuf *ibuf, const char *filepath return true; } -/* Pack image to memory. */ bool BKE_image_memorypack(Image *ima) { bool ok = true; @@ -1153,7 +1204,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; @@ -1173,7 +1224,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); @@ -1256,12 +1307,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; @@ -1283,12 +1339,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; } @@ -1315,6 +1370,9 @@ void BKE_image_print_memlist(Main *bmain) static bool imagecache_check_dirty(ImBuf *ibuf, void *UNUSED(userkey), void *UNUSED(userdata)) { + if (ibuf == NULL) { + return false; + } return (ibuf->userflags & IB_BITMAPDIRTY) == 0; } @@ -1358,19 +1416,21 @@ void BKE_image_free_all_textures(Main *bmain) static bool imagecache_check_free_anim(ImBuf *ibuf, void *UNUSED(userkey), void *userdata) { + if (ibuf == NULL) { + return true; + } int except_frame = *(int *)userdata; return (ibuf->userflags & IB_BITMAPDIRTY) == 0 && (ibuf->index != IMA_NO_INDEX) && (except_frame != IMA_INDEX_ENTRY(ibuf->index)); } -/* 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) @@ -1560,9 +1620,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) { @@ -1583,7 +1643,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: @@ -1625,8 +1685,6 @@ char BKE_imtype_valid_depths(const char imtype) } } -/* string is from command line --render-format arg, keep in sync with - * creator_args.c help info */ char BKE_imtype_from_arg(const char *imtype_arg) { if (STREQ(imtype_arg, "TGA")) { @@ -2037,9 +2095,10 @@ static void stampdata( time_t t; if (scene->r.stamp & R_STAMP_FILENAME) { + const char *blendfile_path = BKE_main_blendfile_path_from_global(); SNPRINTF(stamp_data->file, do_prefix ? "File %s" : "%s", - G.relbase_valid ? BKE_main_blendfile_path_from_global() : "<untitled>"); + (blendfile_path[0] != '\0') ? blendfile_path : "<untitled>"); } else { stamp_data->file[0] = '\0'; @@ -2387,7 +2446,7 @@ void BKE_image_stamp_buf(Scene *scene, /* and draw the text. */ BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.file, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.file, sizeof(stamp_data.file)); /* the extra pixel for background. */ y -= BUFF_MARGIN_Y * 2; @@ -2410,7 +2469,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.date, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.date, sizeof(stamp_data.date)); /* the extra pixel for background. */ y -= BUFF_MARGIN_Y * 2; @@ -2433,7 +2492,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.rendertime, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.rendertime, sizeof(stamp_data.rendertime)); /* the extra pixel for background. */ y -= BUFF_MARGIN_Y * 2; @@ -2456,7 +2515,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.memory, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.memory, sizeof(stamp_data.memory)); /* the extra pixel for background. */ y -= BUFF_MARGIN_Y * 2; @@ -2479,7 +2538,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.hostname, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.hostname, sizeof(stamp_data.hostname)); /* the extra pixel for background. */ y -= BUFF_MARGIN_Y * 2; @@ -2503,7 +2562,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs + (h - h_fixed), 0.0); - BLF_draw_buffer(mono, stamp_data.note, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.note, sizeof(stamp_data.note)); } BLF_disable(mono, BLF_WORD_WRAP); @@ -2527,7 +2586,7 @@ void BKE_image_stamp_buf(Scene *scene, /* and pad the text. */ BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.marker, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.marker, sizeof(stamp_data.marker)); /* space width. */ x += w + pad; @@ -2550,7 +2609,7 @@ void BKE_image_stamp_buf(Scene *scene, /* and pad the text. */ BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.time, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.time, sizeof(stamp_data.time)); /* space width. */ x += w + pad; @@ -2572,7 +2631,7 @@ void BKE_image_stamp_buf(Scene *scene, /* and pad the text. */ BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.frame, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.frame, sizeof(stamp_data.frame)); /* space width. */ x += w + pad; @@ -2592,7 +2651,7 @@ void BKE_image_stamp_buf(Scene *scene, x + w + BUFF_MARGIN_X, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.camera, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.camera, sizeof(stamp_data.camera)); /* space width. */ x += w + pad; @@ -2612,7 +2671,7 @@ void BKE_image_stamp_buf(Scene *scene, x + w + BUFF_MARGIN_X, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.cameralens, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.cameralens, sizeof(stamp_data.cameralens)); } if (TEXT_SIZE_CHECK(stamp_data.scene, w, h)) { @@ -2634,7 +2693,7 @@ void BKE_image_stamp_buf(Scene *scene, /* and pad the text. */ BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.scene, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.scene, sizeof(stamp_data.scene)); } if (TEXT_SIZE_CHECK(stamp_data.strip, w, h)) { @@ -2656,7 +2715,7 @@ void BKE_image_stamp_buf(Scene *scene, y + h + BUFF_MARGIN_Y); BLF_position(mono, x, y + y_ofs, 0.0); - BLF_draw_buffer(mono, stamp_data.strip, BLF_DRAW_STR_DUMMY_MAX); + BLF_draw_buffer(mono, stamp_data.strip, sizeof(stamp_data.strip)); } /* cleanup the buffer. */ @@ -2730,8 +2789,6 @@ static const char *stamp_metadata_fields[] = { NULL, }; -/* Check whether the given metadata field name translates to a known field of - * a stamp. */ bool BKE_stamp_is_known_field(const char *field_name) { int i = 0; @@ -2893,8 +2950,6 @@ bool BKE_imbuf_alpha_test(ImBuf *ibuf) return false; } -/* NOTE: imf->planes is ignored here, its assumed the image channels - * are already set */ void BKE_imbuf_write_prepare(ImBuf *ibuf, const ImageFormatData *imf) { char imtype = imf->imtype; @@ -3071,15 +3126,12 @@ int BKE_imbuf_write(ImBuf *ibuf, const char *name, const ImageFormatData *imf) return ok; } -/* same as BKE_imbuf_write() but crappy workaround not to permanently modify - * _some_, values in the imbuf */ int BKE_imbuf_write_as(ImBuf *ibuf, const char *name, ImageFormatData *imf, const bool save_copy) { 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); @@ -3172,7 +3224,6 @@ struct anim *openanim_noload(const char *name, return anim; } -/* used by sequencer too */ struct anim *openanim(const char *name, int flags, int streamindex, char colorspace[IMA_MAX_SPACE]) { struct anim *anim; @@ -3218,8 +3269,6 @@ struct anim *openanim(const char *name, int flags, int streamindex, char colorsp * -> comes from packedfile or filename or generated */ -/* forces existence of 1 Image for renderout or nodes, returns Image */ -/* name is only for default, when making new one */ Image *BKE_image_ensure_viewer(Main *bmain, int type, const char *name) { Image *ima; @@ -3260,7 +3309,6 @@ static void image_viewer_create_views(const RenderData *rd, Image *ima) } } -/* Reset the image cache and views when the Viewer Nodes views don't match the scene views */ void BKE_image_ensure_viewer_views(const RenderData *rd, Image *ima, ImageUser *iuser) { bool do_reset; @@ -3290,7 +3338,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); @@ -3298,7 +3346,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); @@ -3348,6 +3396,23 @@ static void image_walk_ntree_all_users( } } +static void image_walk_gpu_materials( + ID *id, + ListBase *gpu_materials, + void *customdata, + void callback(Image *ima, ID *iuser_id, ImageUser *iuser, void *customdata)) +{ + LISTBASE_FOREACH (LinkData *, link, gpu_materials) { + GPUMaterial *gpu_material = (GPUMaterial *)link->data; + ListBase textures = GPU_material_textures(gpu_material); + LISTBASE_FOREACH (GPUMaterialTexture *, gpu_material_texture, &textures) { + if (gpu_material_texture->iuser_available) { + callback(gpu_material_texture->ima, id, &gpu_material_texture->iuser, customdata); + } + } + } +} + static void image_walk_id_all_users( ID *id, bool skip_nested_nodes, @@ -3367,6 +3432,7 @@ static void image_walk_id_all_users( if (ma->nodetree && ma->use_nodes && !skip_nested_nodes) { image_walk_ntree_all_users(ma->nodetree, &ma->id, customdata, callback); } + image_walk_gpu_materials(id, &ma->gpumaterial, customdata, callback); break; } case ID_LA: { @@ -3381,6 +3447,7 @@ static void image_walk_id_all_users( if (world->nodetree && world->use_nodes && !skip_nested_nodes) { image_walk_ntree_all_users(world->nodetree, &world->id, customdata, callback); } + image_walk_gpu_materials(id, &world->gpumaterial, customdata, callback); break; } case ID_TE: { @@ -3484,10 +3551,9 @@ 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. */ + /* Must copy image user changes to CoW data-block. */ DEG_id_tag_update(iuser_id, ID_RECALC_COPY_ON_WRITE); } } @@ -3498,12 +3564,11 @@ 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); } if (iuser_id) { - /* Must copy image user changes to CoW datablock. */ + /* Must copy image user changes to CoW data-block. */ DEG_id_tag_update(iuser_id, ID_RECALC_COPY_ON_WRITE); } } @@ -3512,7 +3577,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; } @@ -3565,14 +3629,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); } @@ -3587,7 +3650,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; @@ -3627,10 +3690,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); } @@ -3671,6 +3730,43 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal) BKE_image_free_buffers(ima); } + if (ima->source == IMA_SRC_TILED) { + ListBase new_tiles = {NULL, NULL}; + int new_start, new_range; + + char filepath[FILE_MAX]; + BLI_strncpy(filepath, ima->filepath, sizeof(filepath)); + BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id)); + bool result = BKE_image_get_tile_info(filepath, &new_tiles, &new_start, &new_range); + if (result) { + /* Because the prior and new list of tiles are both sparse sequences, we need to be sure + * to account for how the two sets might or might not overlap. To be complete, we start + * the refresh process by clearing all existing tiles, stopping when there's only 1 tile + * left. */ + while (BKE_image_remove_tile(ima, ima->tiles.last)) { + ; + } + + int remaining_tile_number = ((ImageTile *)ima->tiles.first)->tile_number; + bool needs_final_cleanup = true; + + /* Add in all the new tiles. */ + LISTBASE_FOREACH (LinkData *, new_tile, &new_tiles) { + int new_tile_number = POINTER_AS_INT(new_tile->data); + BKE_image_add_tile(ima, new_tile_number, NULL); + if (new_tile_number == remaining_tile_number) { + needs_final_cleanup = false; + } + } + + /* Final cleanup if the prior remaining tile was never encountered in the new list. */ + if (needs_final_cleanup) { + BKE_image_remove_tile(ima, BKE_image_get_tile(ima, remaining_tile_number)); + } + } + BLI_freelistN(&new_tiles); + } + if (iuser) { image_tag_reload(ima, NULL, iuser, ima); } @@ -3678,7 +3774,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); @@ -3688,30 +3783,13 @@ 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. */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &ima->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &ima->id); + BKE_ntree_update_main(bmain, NULL); } /* return renderpass for a given pass index and active view */ @@ -3772,6 +3850,57 @@ void BKE_image_get_tile_label(Image *ima, ImageTile *tile, char *label, int len_ } } +bool BKE_image_get_tile_info(char *filepath, + ListBase *udim_tiles, + int *udim_start, + int *udim_range) +{ + char filename[FILE_MAXFILE], dirname[FILE_MAXDIR]; + BLI_split_dirfile(filepath, dirname, filename, sizeof(dirname), sizeof(filename)); + + BKE_image_ensure_tile_token(filename); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filename, &tile_format); + + bool is_udim = true; + int min_udim = IMA_UDIM_MAX + 1; + int max_udim = 0; + int id; + + struct direntry *dir; + uint totfile = BLI_filelist_dir_contents(dirname, &dir); + for (int i = 0; i < totfile; i++) { + if (!(dir[i].type & S_IFREG)) { + continue; + } + + if (!BKE_image_get_tile_number_from_filepath(dir[i].relname, udim_pattern, tile_format, &id)) { + continue; + } + + if (id < 1001 || id > IMA_UDIM_MAX) { + is_udim = false; + break; + } + + BLI_addtail(udim_tiles, BLI_genericNodeN(POINTER_FROM_INT(id))); + min_udim = min_ii(min_udim, id); + max_udim = max_ii(max_udim, id); + } + BLI_filelist_free(dir, totfile); + MEM_SAFE_FREE(udim_pattern); + + if (is_udim && min_udim <= IMA_UDIM_MAX) { + BLI_join_dirfile(filepath, FILE_MAX, dirname, filename); + + *udim_start = min_udim; + *udim_range = max_udim - min_udim + 1; + return true; + } + return false; +} + ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label) { if (ima->source != IMA_SRC_TILED) { @@ -3796,7 +3925,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) { @@ -3861,14 +3989,14 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu 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); + 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); + 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); @@ -3927,15 +4055,191 @@ 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; } +void BKE_image_ensure_tile_token(char *filename) +{ + BLI_assert_msg(BLI_path_slash_find(filename) == NULL, + "Only the file-name component should be used!"); + + /* Is there a '<' character in the filename? Assume tokens already present. */ + if (strstr(filename, "<") != NULL) { + return; + } + + /* Is there a sequence of digits in the filename? */ + ushort digits; + char head[FILE_MAX], tail[FILE_MAX]; + BLI_path_sequence_decode(filename, head, tail, &digits); + if (digits == 4) { + sprintf(filename, "%s<UDIM>%s", head, tail); + return; + } + + /* Is there a sequence like u##_v#### in the filename? */ + uint cur = 0; + uint name_end = strlen(filename); + uint u_digits = 0; + uint v_digits = 0; + uint u_start = (uint)-1; + bool u_found = false; + bool v_found = false; + bool sep_found = false; + while (cur < name_end) { + if (filename[cur] == 'u') { + u_found = true; + u_digits = 0; + u_start = cur; + } + else if (filename[cur] == 'v') { + v_found = true; + v_digits = 0; + } + else if (u_found && !v_found) { + if (isdigit(filename[cur]) && u_digits < 2) { + u_digits++; + } + else if (filename[cur] == '_') { + sep_found = true; + } + else { + u_found = false; + } + } + else if (u_found && u_digits > 0 && v_found) { + if (isdigit(filename[cur])) { + if (v_digits < 4) { + v_digits++; + } + else { + u_found = false; + v_found = false; + } + } + else if (v_digits > 0) { + break; + } + } + + cur++; + } + + if (u_found && sep_found && v_found && (u_digits + v_digits > 1)) { + const char *token = "<UVTILE>"; + const size_t token_length = strlen(token); + memmove(filename + u_start + token_length, filename + cur, name_end - cur); + memcpy(filename + u_start, token, token_length); + filename[u_start + token_length + (name_end - cur)] = '\0'; + } +} + +bool BKE_image_tile_filepath_exists(const char *filepath) +{ + BLI_assert(!BLI_path_is_rel(filepath)); + + char dirname[FILE_MAXDIR]; + BLI_split_dir_part(filepath, dirname, sizeof(dirname)); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format); + + bool found = false; + struct direntry *dir; + uint totfile = BLI_filelist_dir_contents(dirname, &dir); + for (int i = 0; i < totfile; i++) { + if (!(dir[i].type & S_IFREG)) { + continue; + } + + int id; + if (!BKE_image_get_tile_number_from_filepath(dir[i].path, udim_pattern, tile_format, &id)) { + continue; + } + + if (id < 1001 || id > IMA_UDIM_MAX) { + continue; + } + + found = true; + break; + } + BLI_filelist_free(dir, totfile); + MEM_SAFE_FREE(udim_pattern); + + return found; +} + +char *BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format) +{ + if (filepath == NULL || r_tile_format == NULL) { + return NULL; + } + + if (strstr(filepath, "<UDIM>") != NULL) { + *r_tile_format = UDIM_TILE_FORMAT_UDIM; + return BLI_str_replaceN(filepath, "<UDIM>", "%d"); + } + if (strstr(filepath, "<UVTILE>") != NULL) { + *r_tile_format = UDIM_TILE_FORMAT_UVTILE; + return BLI_str_replaceN(filepath, "<UVTILE>", "u%d_v%d"); + } + + *r_tile_format = UDIM_TILE_FORMAT_NONE; + return NULL; +} + +bool BKE_image_get_tile_number_from_filepath(const char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int *r_tile_number) +{ + if (filepath == NULL || pattern == NULL || r_tile_number == NULL) { + return false; + } + + int u, v; + bool result = false; + + if (tile_format == UDIM_TILE_FORMAT_UDIM) { + if (sscanf(filepath, pattern, &u) == 1) { + *r_tile_number = u; + result = true; + } + } + else if (tile_format == UDIM_TILE_FORMAT_UVTILE) { + if (sscanf(filepath, pattern, &u, &v) == 2) { + *r_tile_number = 1001 + (u - 1) + ((v - 1) * 10); + result = true; + } + } + + return result; +} + +void BKE_image_set_filepath_from_tile_number(char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int tile_number) +{ + if (filepath == NULL || pattern == NULL) { + return; + } + + if (tile_format == UDIM_TILE_FORMAT_UDIM) { + sprintf(filepath, pattern, tile_number); + } + else if (tile_format == UDIM_TILE_FORMAT_UVTILE) { + int u = ((tile_number - 1001) % 10); + int v = ((tile_number - 1001) / 10); + sprintf(filepath, pattern, u + 1, v + 1); + } +} + /* if layer or pass changes, we need an index for the imbufs list */ /* note it is called for rendered results, but it doesn't use the index! */ -/* and because rendered results use fake layer/passes, don't correct for wrong indices here */ RenderPass *BKE_image_multilayer_index(RenderResult *rr, ImageUser *iuser) { RenderLayer *rl; @@ -3990,7 +4294,6 @@ void BKE_image_multiview_index(Image *ima, ImageUser *iuser) /* if layer or pass changes, we need an index for the imbufs list */ /* note it is called for rendered results, but it doesn't use the index! */ -/* and because rendered results use fake layer/passes, don't correct for wrong indices here */ bool BKE_image_is_multilayer(Image *ima) { if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_SEQUENCE, IMA_SRC_TILED)) { @@ -4205,9 +4508,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) @@ -4243,13 +4544,15 @@ static int image_num_files(Image *ima) } static ImBuf *load_sequence_single( - Image *ima, ImageUser *iuser, int frame, const int view_id, bool *r_assign) + Image *ima, ImageUser *iuser, int frame, const int view_id, bool *r_cache_ibuf) { struct ImBuf *ibuf; char name[FILE_MAX]; int flag; ImageUser iuser_t = {0}; + *r_cache_ibuf = true; + ima->lastframe = frame; if (iuser) { @@ -4289,23 +4592,18 @@ static ImBuf *load_sequence_single( ima->type = IMA_TYPE_MULTILAYER; IMB_freeImBuf(ibuf); ibuf = NULL; + /* NULL ibuf in the cache means the image failed to load. However for multilayer we load + * pixels into RenderResult instead and intentionally leave ibuf NULL. */ + *r_cache_ibuf = false; } } 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; } @@ -4315,11 +4613,11 @@ 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) { + bool put_in_cache; + ibuf = load_sequence_single(ima, iuser, frame, 0, &put_in_cache); + if (put_in_cache) { image_assign_ibuf(ima, ibuf, 0, entry); } } @@ -4328,9 +4626,10 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry, struct ImBuf **ibuf_arr; ibuf_arr = MEM_mallocN(sizeof(ImBuf *) * totviews, "Image Views Imbufs"); + bool *cache_ibuf_arr = MEM_mallocN(sizeof(bool) * totviews, "Image View Put In Cache"); 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, cache_ibuf_arr + i); } if (BKE_image_is_stereo(ima) && ima->views_format == R_IMF_VIEWS_STEREO_3D) { @@ -4340,8 +4639,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++) { + for (int i = 0; i < totviews; i++) { + if (cache_ibuf_arr[i]) { image_assign_ibuf(ima, ibuf_arr[i], i, entry); } } @@ -4355,6 +4654,7 @@ static ImBuf *image_load_sequence_file(Image *ima, ImageUser *iuser, int entry, /* cleanup */ MEM_freeN(ibuf_arr); + MEM_freeN(cache_ibuf_arr); } return ibuf; @@ -4363,7 +4663,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 */ @@ -4405,13 +4704,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; } @@ -4423,8 +4715,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; @@ -4466,12 +4756,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; @@ -4482,7 +4766,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); @@ -4513,12 +4796,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 */ @@ -4535,10 +4813,6 @@ static ImBuf *image_load_movie_file(Image *ima, ImageUser *iuser, int frame) MEM_freeN(ibuf_arr); } - if (iuser) { - iuser->ok = tile->ok; - } - return ibuf; } @@ -4547,12 +4821,14 @@ static ImBuf *load_image_single(Image *ima, int cfra, const int view_id, const bool has_packed, - bool *r_assign) + bool *r_cache_ibuf) { char filepath[FILE_MAX]; struct ImBuf *ibuf = NULL; int flag; + *r_cache_ibuf = true; + /* is there a PackedFile with this image ? */ if (has_packed) { ImagePackedFile *imapf; @@ -4603,15 +4879,17 @@ static ImBuf *load_image_single(Image *ima, ima->type = IMA_TYPE_MULTILAYER; IMB_freeImBuf(ibuf); ibuf = NULL; + /* NULL ibuf in the cache means the image failed to load. However for multilayer we load + * pixels into RenderResult instead and intentionally leave ibuf NULL. */ + *r_cache_ibuf = false; } } else #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); @@ -4622,10 +4900,6 @@ static ImBuf *load_image_single(Image *ima, } } } - else { - ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser); - tile->ok = 0; - } return ibuf; } @@ -4636,7 +4910,6 @@ static ImBuf *load_image_single(Image *ima, 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); @@ -4653,8 +4926,9 @@ 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) { + bool put_in_cache; + ibuf = load_image_single(ima, iuser, cfra, 0, has_packed, &put_in_cache); + if (put_in_cache) { image_assign_ibuf(ima, ibuf, IMA_NO_INDEX, 0); } } @@ -4664,9 +4938,10 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra) BLI_assert(totviews > 0); ibuf_arr = MEM_callocN(sizeof(ImBuf *) * totviews, "Image Views Imbufs"); + bool *cache_ibuf_arr = MEM_mallocN(sizeof(bool) * totviews, "Image Views Put In Cache"); 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, cache_ibuf_arr + i); } /* multi-views/multi-layers OpenEXR files directly populate ima, and return NULL ibuf... */ @@ -4679,8 +4954,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++) { + for (i = 0; i < totviews; i++) { + if (cache_ibuf_arr[i]) { image_assign_ibuf(ima, ibuf_arr[i], i, 0); } } @@ -4694,11 +4969,7 @@ static ImBuf *image_load_image_file(Image *ima, ImageUser *iuser, int cfra) /* cleanup */ MEM_freeN(ibuf_arr); - } - - if (iuser) { - ImageTile *tile = BKE_image_get_tile(ima, 0); - iuser->ok = tile->ok; + MEM_freeN(cache_ibuf_arr); } return ibuf; @@ -4733,14 +5004,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; } @@ -4858,7 +5121,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) { @@ -4935,9 +5198,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; } @@ -4994,7 +5254,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); @@ -5002,42 +5263,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 @@ -5047,17 +5296,7 @@ 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 = image_get_tile_number_from_iuser(ima, iuser); - 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; - } - } + ibuf = image_get_cached_ibuf_for_index_entry(ima, index, entry, r_is_cached_empty); } } @@ -5078,26 +5317,18 @@ 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; } -/* Checks optional ImageUser and verifies/creates ImBuf. +/** + * Checks optional #ImageUser and verifies/creates #ImBuf. * - * not thread-safe, so callee should worry about thread locks + * \warning Not thread-safe, so callee should worry about thread locks. */ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock) { @@ -5113,7 +5344,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 */ @@ -5175,8 +5410,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) { @@ -5193,7 +5426,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 */ @@ -5216,22 +5449,22 @@ static ImBuf *image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock) return ibuf; } -/* return image buffer for given image and user - * - * - will lock render result if image type is render result and lock is not NULL - * - will return NULL if image type if render or composite result and lock is NULL - * - * references the result, BKE_image_release_ibuf should be used to de-reference - */ ImBuf *BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock) { + /* NOTE: same as #image_acquire_ibuf, but can be used to retrieve images being rendered in + * a thread safe way, always call both acquire and release. */ + + if (ima == NULL) { + return NULL; + } + 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; } @@ -5250,13 +5483,12 @@ 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); } } -/* checks whether there's an image buffer for given image and user */ bool BKE_image_has_ibuf(Image *ima, ImageUser *iuser) { ImBuf *ibuf; @@ -5266,15 +5498,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); @@ -5294,6 +5526,7 @@ typedef struct ImagePoolItem { typedef struct ImagePool { ListBase image_buffers; BLI_mempool *memory_pool; + ThreadMutex mutex; } ImagePool; ImagePool *BKE_image_pool_new(void) @@ -5301,21 +5534,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); } @@ -5347,28 +5587,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; @@ -5379,7 +5625,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; } @@ -5489,18 +5735,6 @@ void BKE_image_user_frame_calc(Image *ima, ImageUser *iuser, int cfra) 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; } } @@ -5539,7 +5773,7 @@ static void image_user_id_has_animation(Image *ima, bool BKE_image_user_id_has_animation(ID *id) { /* For the dependency graph, this does not consider nested node - * trees as these are handled as their own datablock. */ + * trees as these are handled as their own data-block. */ bool has_animation = false; bool skip_nested_nodes = true; image_walk_id_all_users(id, skip_nested_nodes, &has_animation, image_user_id_has_animation); @@ -5576,6 +5810,11 @@ void BKE_image_user_id_eval_animation(Depsgraph *depsgraph, ID *id) void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath) { + BKE_image_user_file_path_ex(iuser, ima, filepath, true); +} + +void BKE_image_user_file_path_ex(ImageUser *iuser, Image *ima, char *filepath, bool resolve_udim) +{ if (BKE_image_is_multiview(ima)) { ImageView *iv = BLI_findlink(&ima->views, iuser->view); if (iv->filepath[0]) { @@ -5596,13 +5835,17 @@ void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath) int index; if (ima->source == IMA_SRC_SEQUENCE) { index = iuser ? iuser->framenr : ima->lastframe; + BLI_path_sequence_decode(filepath, head, tail, &numlen); + BLI_path_sequence_encode(filepath, head, tail, numlen, index); } - else { + else if (resolve_udim) { index = image_get_tile_number_from_iuser(ima, iuser); - } - BLI_path_sequence_decode(filepath, head, tail, &numlen); - BLI_path_sequence_encode(filepath, head, tail, numlen, index); + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format); + BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, tile_format, index); + MEM_SAFE_FREE(udim_pattern); + } } BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id)); @@ -5750,7 +5993,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); } @@ -5762,31 +6005,28 @@ bool BKE_image_has_filepath(Image *ima) return ima->filepath[0] != '\0'; } -/* Checks the image buffer changes with time (not keyframed values). */ bool BKE_image_is_animated(Image *image) { return ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE); } -/* Checks whether the image consists of multiple buffers. */ bool BKE_image_has_multiple_ibufs(Image *image) { return ELEM(image->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED); } -/* Image modifications */ 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; @@ -5795,7 +6035,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; @@ -5824,55 +6064,57 @@ 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); while (!IMB_moviecacheIter_done(iter)) { - has_loaded_ibuf = true; - break; + ImBuf *ibuf = IMB_moviecacheIter_getImBuf(iter); + if (ibuf != NULL) { + has_loaded_ibuf = true; + break; + } + IMB_moviecacheIter_step(iter); } IMB_moviecacheIter_free(iter); } - BLI_mutex_unlock(image_mutex); + BLI_mutex_unlock(image->runtime.cache_mutex); return has_loaded_ibuf; } -/** - * References the result, #BKE_image_release_ibuf is to be called to de-reference. - * Use lock=NULL when calling #BKE_image_release_ibuf(). - */ 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; @@ -5881,36 +6123,29 @@ 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; } -/** - * References the result, #BKE_image_release_ibuf is to be called to de-reference. - * Use lock=NULL when calling #BKE_image_release_ibuf(). - * - * TODO(sergey): This is actually "get first item from the cache", which is - * not so much predictable. But using first loaded image buffer - * was also malicious logic and all the areas which uses this - * function are to be re-considered. - */ 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..bef14b6ad70 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)) { @@ -370,7 +369,7 @@ static void checker_board_text( char text[3] = {'A', '1', '\0'}; const int mono = blf_mono_font_render; - BLF_size(mono, 54, 72); /* hard coded size! */ + BLF_size(mono, 54.0f, 72); /* hard coded size! */ /* OCIO_TODO: using NULL as display will assume using sRGB display * this is correct since currently generated images are assumed to be in sRGB space, diff --git a/source/blender/blenkernel/intern/image_gpu.c b/source/blender/blenkernel/intern/image_gpu.cc index 9712e912bed..c82de02e52a 100644 --- a/source/blender/blenkernel/intern/image_gpu.c +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -47,7 +47,7 @@ #include "PIL_time.h" /* Prototypes. */ -static void gpu_free_unused_buffers(void); +static void gpu_free_unused_buffers(); 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( @@ -55,13 +55,12 @@ static void image_update_gputexture_ex( /* Internal structs. */ #define IMA_PARTIAL_REFRESH_TILE_SIZE 256 -typedef struct ImagePartialRefresh { +struct ImagePartialRefresh { struct ImagePartialRefresh *next, *prev; int tile_x; int tile_y; -} ImagePartialRefresh; +}; -/* Is the alpha of the `GPUTexture` for a given image/ibuf premultiplied. */ bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf) { if (image) { @@ -71,7 +70,7 @@ bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf) } /* Generated images use pre multiplied float buffer, but straight alpha for byte buffers. */ if (image->type == IMA_TYPE_UV_TEST && ibuf) { - return ibuf->rect_float != NULL; + return ibuf->rect_float != nullptr; } } if (ibuf) { @@ -85,8 +84,9 @@ bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf) } /* -------------------------------------------------------------------- */ -/** \name UDIM gpu texture +/** \name UDIM GPU Texture * \{ */ + static bool is_over_resolution_limit(int w, int h, bool limit_gl_texture_size) { return (w > GPU_texture_size_with_limit(w, limit_gl_texture_size) || @@ -104,8 +104,8 @@ static GPUTexture *gpu_texture_create_tile_mapping( 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; + if (tilearray == nullptr) { + return nullptr; } float array_w = GPU_texture_width(tilearray); @@ -142,11 +142,11 @@ static GPUTexture *gpu_texture_create_tile_mapping( return tex; } -typedef struct PackTile { +struct PackTile { FixedSizeBoxPack boxpack; ImageTile *tile; float pack_score; -} PackTile; +}; static int compare_packtile(const void *a, const void *b) { @@ -163,16 +163,16 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, 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}; + ListBase boxes = {nullptr}; LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { ImageUser iuser; BKE_imageuser_default(&iuser); iuser.tile = tile->tile_number; - ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); + ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr); if (ibuf) { - PackTile *packtile = (PackTile *)MEM_callocN(sizeof(PackTile), __func__); + PackTile *packtile = MEM_cnew<PackTile>(__func__); packtile->tile = tile; packtile->boxpack.w = ibuf->x; packtile->boxpack.h = ibuf->y; @@ -190,7 +190,7 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, float w = packtile->boxpack.w, h = packtile->boxpack.h; packtile->pack_score = max_ff(w, h) / min_ff(w, h) * w * h; - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); BLI_addtail(&boxes, packtile); } } @@ -200,10 +200,10 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, BLI_listbase_sort(&boxes, compare_packtile); int arraylayers = 0; /* Keep adding layers until all tiles are packed. */ - while (boxes.first != NULL) { - ListBase packed = {NULL}; + while (boxes.first != nullptr) { + ListBase packed = {nullptr}; BLI_box_pack_2d_fixedarea(&boxes, arraywidth, arrayheight, &packed); - BLI_assert(packed.first != NULL); + BLI_assert(packed.first != nullptr); LISTBASE_FOREACH (PackTile *, packtile, &packed) { ImageTile *tile = packtile->tile; @@ -241,7 +241,7 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImageUser iuser; BKE_imageuser_default(&iuser); iuser.tile = tile->tile_number; - ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, NULL); + ImBuf *ibuf = BKE_image_acquire_ibuf(ima, &iuser, nullptr); if (ibuf) { const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf); @@ -254,7 +254,7 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, store_premultiplied); } - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); } if (GPU_mipmap_enabled()) { @@ -305,7 +305,7 @@ static GPUTexture **get_image_gpu_texture_ptr(Image *ima, if (in_range) { return &(ima->gputexture[textarget][multiview_eye][resolution]); } - return NULL; + return nullptr; } static GPUTexture *image_gpu_texture_error_create(eGPUTextureTarget textarget) @@ -342,8 +342,8 @@ static GPUTexture *image_get_gpu_texture(Image *ima, ImBuf *ibuf, eGPUTextureTarget textarget) { - if (ima == NULL) { - return NULL; + if (ima == nullptr) { + return nullptr; } /* Free any unused GPU textures, since we know we are in a thread with OpenGL @@ -373,17 +373,17 @@ static GPUTexture *image_get_gpu_texture(Image *ima, /* 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 == nullptr || tile == nullptr) && ((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))) { + while (( + refresh_area = static_cast<ImagePartialRefresh *>(BLI_pophead(&ima->gpu_refresh_areas)))) { const int tile_offset_x = refresh_area->tile_x * IMA_PARTIAL_REFRESH_TILE_SIZE; const int tile_offset_y = refresh_area->tile_y * IMA_PARTIAL_REFRESH_TILE_SIZE; const int tile_width = MIN2(IMA_PARTIAL_REFRESH_TILE_SIZE, ibuf->x - tile_offset_x); @@ -405,7 +405,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } const bool limit_resolution = U.glreslimit != 0 && ((iuser && (iuser->flag & IMA_SHOW_MAX_RESOLUTION) == 0) || - (iuser == NULL)) && + (iuser == nullptr)) && ((ima->gpuflag & IMA_GPU_REUSE_MAX_RESOLUTION) == 0); const eImageTextureResolution texture_resolution = limit_resolution ? IMA_TEXTURE_RESOLUTION_LIMITED : @@ -417,16 +417,16 @@ static GPUTexture *image_get_gpu_texture(Image *ima, /* 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 == nullptr) { *tex = image_gpu_texture_error_create(textarget); return *tex; } /* check if we have a valid image buffer */ ImBuf *ibuf_intern = ibuf; - if (ibuf_intern == NULL) { - ibuf_intern = BKE_image_acquire_ibuf(ima, iuser, NULL); - if (ibuf_intern == NULL) { + if (ibuf_intern == nullptr) { + ibuf_intern = BKE_image_acquire_ibuf(ima, iuser, nullptr); + if (ibuf_intern == nullptr) { *tex = image_gpu_texture_error_create(textarget); return *tex; } @@ -478,8 +478,8 @@ static GPUTexture *image_get_gpu_texture(Image *ima, } /* if `ibuf` was given, we don't own the `ibuf_intern` */ - if (ibuf == NULL) { - BKE_image_release_ibuf(ima, ibuf_intern, NULL); + if (ibuf == nullptr) { + BKE_image_release_ibuf(ima, ibuf_intern, nullptr); } if (*tex) { @@ -513,19 +513,19 @@ GPUTexture *BKE_image_get_gpu_tilemap(Image *image, ImageUser *iuser, ImBuf *ibu * In that case we push them into a queue and free the buffers later. * \{ */ -static LinkNode *gpu_texture_free_queue = NULL; +static LinkNode *gpu_texture_free_queue = nullptr; static ThreadMutex gpu_texture_queue_mutex = BLI_MUTEX_INITIALIZER; -static void gpu_free_unused_buffers(void) +static void gpu_free_unused_buffers() { - if (gpu_texture_free_queue == NULL) { + if (gpu_texture_free_queue == nullptr) { return; } BLI_mutex_lock(&gpu_texture_queue_mutex); - while (gpu_texture_free_queue != NULL) { - GPUTexture *tex = BLI_linklist_pop(&gpu_texture_free_queue); + while (gpu_texture_free_queue != nullptr) { + GPUTexture *tex = static_cast<GPUTexture *>(BLI_linklist_pop(&gpu_texture_free_queue)); GPU_texture_free(tex); } @@ -550,7 +550,7 @@ 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++) { for (int resolution = 0; resolution < IMA_TEXTURE_RESOLUTION_LEN; resolution++) { - if (ima->gputexture[i][eye][resolution] != NULL) { + if (ima->gputexture[i][eye][resolution] != nullptr) { if (immediate) { GPU_texture_free(ima->gputexture[i][eye][resolution]); } @@ -560,7 +560,7 @@ static void image_free_gpu(Image *ima, const bool immediate) BLI_mutex_unlock(&gpu_texture_queue_mutex); } - ima->gputexture[i][eye][resolution] = NULL; + ima->gputexture[i][eye][resolution] = nullptr; } } } @@ -574,9 +574,9 @@ 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) { + if (ima->gputexture[i][eye][resolution] != nullptr) { GPU_texture_free(ima->gputexture[i][eye][resolution]); - ima->gputexture[i][eye][resolution] = NULL; + ima->gputexture[i][eye][resolution] = nullptr; } } } @@ -598,7 +598,6 @@ void BKE_image_free_all_gputextures(Main *bmain) } } -/* same as above but only free animated images */ void BKE_image_free_anim_gputextures(Main *bmain) { if (bmain) { @@ -645,6 +644,7 @@ void BKE_image_free_old_gputextures(Main *bmain) } } } + /** \} */ /* -------------------------------------------------------------------- */ @@ -770,7 +770,7 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, { const int resolution = texture_resolution == IMA_TEXTURE_RESOLUTION_LIMITED ? 1 : 0; bool scaled; - if (tile != NULL) { + if (tile != nullptr) { ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; int *tilesize = tile_runtime->tilearray_size; scaled = (ibuf->x != tilesize[0]) || (ibuf->y != tilesize[1]); @@ -797,14 +797,14 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, int tex_offset = ibuf->channels * (y * ibuf->x + x); const bool store_premultiplied = BKE_image_has_gpu_texture_premultiplied_alpha(ima, ibuf); - if (rect_float == NULL) { + if (rect_float == nullptr) { /* Byte pixels. */ if (!IMB_colormanagement_space_is_data(ibuf->rect_colorspace)) { const bool compress_as_srgb = !IMB_colormanagement_space_is_scene_linear( ibuf->rect_colorspace); rect = (uchar *)MEM_mallocN(sizeof(uchar[4]) * w * h, __func__); - if (rect == NULL) { + if (rect == nullptr) { return; } @@ -821,7 +821,7 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, /* Float pixels. */ if (ibuf->channels != 4 || scaled || !store_premultiplied) { rect_float = (float *)MEM_mallocN(sizeof(float[4]) * w * h, __func__); - if (rect_float == NULL) { + if (rect_float == nullptr) { return; } @@ -835,7 +835,7 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, if (scaled) { /* Slower update where we first have to scale the input pixels. */ - if (tile != NULL) { + if (tile != nullptr) { ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; int *tileoffset = tile_runtime->tilearray_offset; int *tilesize = tile_runtime->tilearray_size; @@ -845,12 +845,12 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, } else { gpu_texture_update_scaled( - tex, rect, rect_float, ibuf->x, ibuf->y, x, y, -1, NULL, NULL, w, h); + tex, rect, rect_float, ibuf->x, ibuf->y, x, y, -1, nullptr, nullptr, w, h); } } else { /* Fast update at same resolution. */ - if (tile != NULL) { + if (tile != nullptr) { ImageTile_RuntimeTextureSlot *tile_runtime = &tile->runtime.slots[resolution]; int *tileoffset = tile_runtime->tilearray_offset; int tilelayer = tile_runtime->tilearray_layer; @@ -859,7 +859,7 @@ static void gpu_texture_update_from_ibuf(GPUTexture *tex, } else { gpu_texture_update_unscaled( - tex, rect, rect_float, x, y, -1, NULL, w, h, tex_stride, tex_offset); + tex, rect, rect_float, x, y, -1, nullptr, w, h, tex_stride, tex_offset); } } @@ -887,39 +887,33 @@ static void image_update_gputexture_ex( 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; + eImageTextureResolution texture_resolution = static_cast<eImageTextureResolution>(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); + if (tex != nullptr && tile == ima->tiles.first) { + gpu_texture_update_from_ibuf(tex, ima, ibuf, nullptr, x, y, w, h, texture_resolution); } /* Check if we need to update the array gputexture. */ tex = ima->gputexture[TEXTARGET_2D_ARRAY][eye][resolution]; - if (tex != NULL) { + if (tex != nullptr) { gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h, texture_resolution); } } } -/* Partial update of texture for texture painting. This is often much - * quicker than fully updating the texture for high resolution images. */ void BKE_image_update_gputexture(Image *ima, ImageUser *iuser, int x, int y, int w, int h) { - ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, NULL); + ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, nullptr); ImageTile *tile = BKE_image_get_tile_from_iuser(ima, iuser); - if ((ibuf == NULL) || (w == 0) || (h == 0)) { + if ((ibuf == nullptr) || (w == 0) || (h == 0)) { /* Full reload of texture. */ BKE_image_free_gputextures(ima); } image_update_gputexture_ex(ima, tile, ibuf, x, y, w, h); - BKE_image_release_ibuf(ima, ibuf, NULL); + BKE_image_release_ibuf(ima, ibuf, nullptr); } -/* Mark areas on the GPUTexture that needs to be updated. The areas are marked in chunks. - * The next time the GPUTexture is used these tiles will be refreshes. This saves time - * when writing to the same place multiple times This happens for during foreground - * rendering. */ void BKE_image_update_gputexture_delayed( struct Image *ima, struct ImBuf *ibuf, int x, int y, int w, int h) { @@ -947,7 +941,7 @@ void BKE_image_update_gputexture_delayed( const int num_tiles_y = (end_tile_y + 1) - (start_tile_y); const int num_tiles = num_tiles_x * num_tiles_y; const bool allocate_on_heap = BLI_BITMAP_SIZE(num_tiles) > 16; - BLI_bitmap *requested_tiles = NULL; + BLI_bitmap *requested_tiles = nullptr; if (allocate_on_heap) { requested_tiles = BLI_BITMAP_NEW(num_tiles, __func__); } @@ -977,7 +971,8 @@ void BKE_image_update_gputexture_delayed( for (int tile_y = start_tile_y; tile_y <= end_tile_y; tile_y++) { for (int tile_x = start_tile_x; tile_x <= end_tile_x; tile_x++) { if (!BLI_BITMAP_TEST_BOOL(requested_tiles, tile_index)) { - ImagePartialRefresh *area = MEM_mallocN(sizeof(ImagePartialRefresh), __func__); + ImagePartialRefresh *area = static_cast<ImagePartialRefresh *>( + MEM_mallocN(sizeof(ImagePartialRefresh), __func__)); area->tile_x = tile_x; area->tile_y = tile_y; BLI_addtail(&ima->gpu_refresh_areas, area); @@ -992,10 +987,6 @@ void BKE_image_update_gputexture_delayed( } } -/* these two functions are called on entering and exiting texture paint mode, - * temporary disabling/enabling mipmapping on all images for quick texture - * updates with glTexSubImage2D. images that didn't change don't have to be - * re-uploaded to OpenGL */ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap) { LISTBASE_FOREACH (Image *, ima, &bmain->images) { @@ -1006,7 +997,7 @@ void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap) 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) { + if (tex != nullptr) { 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 f93ede517a9..329bc7b498b 100644 --- a/source/blender/blenkernel/intern/image_save.c +++ b/source/blender/blenkernel/intern/image_save.c @@ -30,6 +30,8 @@ #include "DNA_image_types.h" +#include "MEM_guardedalloc.h" + #include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -402,15 +404,17 @@ bool BKE_image_save( bool colorspace_changed = false; + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = NULL; + if (ima->source == IMA_SRC_TILED) { - /* Verify filepath for tiles images. */ - ImageTile *first_tile = ima->tiles.first; - if (BLI_path_sequence_decode(opts->filepath, NULL, NULL, NULL) != first_tile->tile_number) { + /* Verify filepath for tiled images contains a valid UDIM marker. */ + udim_pattern = BKE_image_get_tile_strformat(opts->filepath, &tile_format); + if (tile_format == UDIM_TILE_FORMAT_NONE) { BKE_reportf(reports, RPT_ERROR, - "When saving a tiled image, the path '%s' must contain the UDIM tile number %d", - opts->filepath, - first_tile->tile_number); + "When saving a tiled image, the path '%s' must contain a valid UDIM marker", + opts->filepath); return false; } @@ -420,36 +424,29 @@ bool BKE_image_save( } } - /* Save image - or, for tiled images, the first tile. */ - bool ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); - - if (ok && ima->source == IMA_SRC_TILED) { + /* Save images */ + bool ok = false; + if (ima->source != IMA_SRC_TILED) { + ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); + } + else { char filepath[FILE_MAX]; BLI_strncpy(filepath, opts->filepath, sizeof(filepath)); - char head[FILE_MAX], tail[FILE_MAX]; - unsigned short numlen; - BLI_path_sequence_decode(filepath, head, tail, &numlen); - - /* Save all other tiles. */ - int index; - LISTBASE_FOREACH_INDEX (ImageTile *, tile, &ima->tiles, index) { - /* First tile was already saved before the loop. */ - if (index == 0) { - continue; - } + /* Save all the tiles. */ + LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { + BKE_image_set_filepath_from_tile_number( + opts->filepath, udim_pattern, tile_format, tile->tile_number); + iuser->tile = tile->tile_number; + ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); if (!ok) { - continue; + break; } - - /* Build filepath of the tile. */ - BLI_path_sequence_encode(opts->filepath, head, tail, numlen, tile->tile_number); - - iuser->tile = tile->tile_number; - ok = ok && image_save_single(reports, ima, iuser, opts, &colorspace_changed); } + BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath)); + MEM_freeN(udim_pattern); } if (colorspace_changed) { diff --git a/source/blender/blenkernel/intern/ipo.c b/source/blender/blenkernel/intern/ipo.c index 26a1240080f..d87331fd65c 100644 --- a/source/blender/blenkernel/intern/ipo.c +++ b/source/blender/blenkernel/intern/ipo.c @@ -185,6 +185,7 @@ IDTypeInfo IDType_ID_IP = { .name_plural = "ipos", .translation_context = "", .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = NULL, .copy_data = NULL, @@ -192,6 +193,7 @@ IDTypeInfo IDType_ID_IP = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = NULL, @@ -2093,17 +2095,6 @@ static bool seq_convert_callback(Sequence *seq, void *userdata) /* *************************************************** */ /* External API - Only Called from do_versions() */ -/* Called from do_versions() in readfile.c to convert the old 'IPO/adrcode' system - * to the new 'Animato/RNA' system. - * - * The basic method used here, is to loop over data-blocks which have IPO-data, - * and add those IPO's to new AnimData blocks as Actions. - * Action/NLA data only works well for Objects, so these only need to be checked for there. - * - * Data that has been converted should be freed immediately, which means that it is immediately - * clear which data-blocks have yet to be converted, and also prevent freeing errors when we exit. - */ -/* XXX currently done after all file reading... */ void do_versions_ipos_to_animato(Main *bmain) { ListBase drivers = {NULL, NULL}; diff --git a/source/blender/blenkernel/intern/key.c b/source/blender/blenkernel/intern/key.c index 44fc86877a7..0df493e28c0 100644 --- a/source/blender/blenkernel/intern/key.c +++ b/source/blender/blenkernel/intern/key.c @@ -213,6 +213,7 @@ IDTypeInfo IDType_ID_KE = { .name_plural = "shape_keys", .translation_context = BLT_I18NCONTEXT_ID_SHAPEKEY, .flags = IDTYPE_FLAGS_NO_LIBLINKING, + .asset_type_info = NULL, .init_data = NULL, .copy_data = shapekey_copy_data, @@ -220,6 +221,7 @@ IDTypeInfo IDType_ID_KE = { .make_local = NULL, .foreach_id = shapekey_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, /* A bit weird, due to shapekeys not being strictly speaking embedded data... But they also * share a lot with those (non linkable, only ever used by one owner ID, etc.). */ .owner_get = shapekey_owner_get, @@ -244,7 +246,6 @@ typedef struct WeightsArrayCache { float **defgroup_weights; } WeightsArrayCache; -/** Free (or release) any data used by this shapekey (does not free the key itself). */ void BKE_key_free_data(Key *key) { shapekey_free_data(&key->id); @@ -314,11 +315,6 @@ Key *BKE_key_add(Main *bmain, ID *id) /* common function */ return key; } -/** - * 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; @@ -392,7 +388,6 @@ void key_curve_position_weights(float t, float data[4], int type) } } -/* first derivative */ void key_curve_tangent_weights(float t, float data[4], int type) { float t2, fc; @@ -431,7 +426,6 @@ void key_curve_tangent_weights(float t, float data[4], int type) } } -/* second derivative */ void key_curve_normal_weights(float t, float data[4], int type) { float fc; @@ -1522,7 +1516,6 @@ static void do_latt_key(Object *ob, Key *key, char *out, const int tot) } } -/* returns key coordinates (+ tilt) when key applied, NULL otherwise */ float *BKE_key_evaluate_object_ex(Object *ob, int *r_totelem, float *arr, size_t arr_size) { Key *key = BKE_key_from_object(ob); @@ -1624,9 +1617,6 @@ float *BKE_key_evaluate_object(Object *ob, int *r_totelem) return BKE_key_evaluate_object_ex(ob, r_totelem, NULL, 0); } -/** - * \param shape_index: The index to use or all (when -1). - */ int BKE_keyblock_element_count_from_shape(const Key *key, const int shape_index) { int result = 0; @@ -1644,9 +1634,6 @@ int BKE_keyblock_element_count(const Key *key) return BKE_keyblock_element_count_from_shape(key, -1); } -/** - * \param shape_index: The index to use or all (when -1). - */ size_t BKE_keyblock_element_calc_size_from_shape(const Key *key, const int shape_index) { return (size_t)BKE_keyblock_element_count_from_shape(key, shape_index) * key->elemsize; @@ -1664,9 +1651,6 @@ size_t BKE_keyblock_element_calc_size(const Key *key) * use #BKE_keyblock_element_calc_size to allocate the size of the data needed. * \{ */ -/** - * \param shape_index: The index to use or all (when -1). - */ void BKE_keyblock_data_get_from_shape(const Key *key, float (*arr)[3], const int shape_index) { uint8_t *elements = (uint8_t *)arr; @@ -1685,9 +1669,6 @@ void BKE_keyblock_data_get(const Key *key, float (*arr)[3]) BKE_keyblock_data_get_from_shape(key, arr, -1); } -/** - * Set the data to all key-blocks (or shape_index if != -1). - */ void BKE_keyblock_data_set_with_mat4(Key *key, const int shape_index, const float (*coords)[3], @@ -1715,10 +1696,6 @@ void BKE_keyblock_data_set_with_mat4(Key *key, } } -/** - * Set the data for all key-blocks (or shape_index if != -1), - * transforming by \a mat. - */ void BKE_keyblock_curve_data_set_with_mat4( Key *key, const ListBase *nurb, const int shape_index, const void *data, const float mat[4][4]) { @@ -1734,9 +1711,6 @@ void BKE_keyblock_curve_data_set_with_mat4( } } -/** - * Set the data for all key-blocks (or shape_index if != -1). - */ void BKE_keyblock_data_set(Key *key, const int shape_index, const void *data) { const uint8_t *elements = data; @@ -1868,14 +1842,6 @@ KeyBlock *BKE_keyblock_add(Key *key, const char *name) return kb; } -/** - * \note sorting is a problematic side effect in some cases, - * better only do this explicitly by having its own function, - * - * \param key: The key datablock to add to. - * \param name: Optional name for the new keyblock. - * \param do_force: always use ctime even for relative keys. - */ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force) { KeyBlock *kb = BKE_keyblock_add(key, name); @@ -1904,7 +1870,6 @@ KeyBlock *BKE_keyblock_add_ctime(Key *key, const char *name, const bool do_force return kb; } -/* only the active keyblock */ KeyBlock *BKE_keyblock_from_object(Object *ob) { Key *key = BKE_key_from_object(ob); @@ -1928,7 +1893,6 @@ KeyBlock *BKE_keyblock_from_object_reference(Object *ob) return NULL; } -/* get the appropriate KeyBlock given an index */ KeyBlock *BKE_keyblock_from_key(Key *key, int index) { if (key) { @@ -1946,15 +1910,11 @@ KeyBlock *BKE_keyblock_from_key(Key *key, int index) return NULL; } -/* get the appropriate KeyBlock given a name to search for */ KeyBlock *BKE_keyblock_find_name(Key *key, const char name[]) { return BLI_findstring(&key->block, name, offsetof(KeyBlock, name)); } -/** - * \brief copy shape-key attributes, but not key data.or name/uid - */ void BKE_keyblock_copy_settings(KeyBlock *kb_dst, const KeyBlock *kb_src) { kb_dst->pos = kb_src->pos; @@ -1966,9 +1926,6 @@ void BKE_keyblock_copy_settings(KeyBlock *kb_dst, const KeyBlock *kb_src) kb_dst->slidermax = kb_src->slidermax; } -/* Get RNA-Path for 'value' setting of the given ShapeKey - * NOTE: the user needs to free the returned string once they're finish with it - */ char *BKE_keyblock_curval_rnapath_get(Key *key, KeyBlock *kb) { PointerRNA ptr; @@ -1991,6 +1948,7 @@ char *BKE_keyblock_curval_rnapath_get(Key *key, KeyBlock *kb) /* conversion functions */ /************************* Lattice ************************/ + void BKE_keyblock_update_from_lattice(Lattice *lt, KeyBlock *kb) { BPoint *bp; @@ -2191,6 +2149,7 @@ void BKE_keyblock_convert_to_curve(KeyBlock *kb, Curve *UNUSED(cu), ListBase *nu } /************************* Mesh ************************/ + void BKE_keyblock_update_from_mesh(Mesh *me, KeyBlock *kb) { MVert *mvert; @@ -2243,15 +2202,6 @@ 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 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. - */ void BKE_keyblock_mesh_calc_normals(struct KeyBlock *kb, struct Mesh *mesh, float (*r_vertnors)[3], @@ -2280,13 +2230,20 @@ 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_and_vertex( - me.mvert, me.totvert, me.mloop, me.totloop, me.mpoly, me.totpoly, r_polynors, r_vertnors); + + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); + if (r_vertnors) { + memcpy(r_vertnors, vert_normals, sizeof(float[3]) * me.totvert); + } + + const float(*face_normals)[3] = BKE_mesh_poly_normals_ensure(mesh); + memcpy(r_polynors, face_normals, sizeof(float[3]) * me.totpoly); if (r_loopnors) { short(*clnors)[2] = CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); /* May be NULL. */ BKE_mesh_normals_loop_split(me.mvert, + vert_normals, me.totvert, me.medge, me.totedge, @@ -2294,7 +2251,7 @@ void BKE_keyblock_mesh_calc_normals(struct KeyBlock *kb, r_loopnors, me.totloop, me.mpoly, - r_polynors, + face_normals, me.totpoly, (me.flag & ME_AUTOSMOOTH) != 0, me.smoothresh, @@ -2316,6 +2273,7 @@ void BKE_keyblock_mesh_calc_normals(struct KeyBlock *kb, } /************************* raw coords ************************/ + void BKE_keyblock_update_from_vertcos(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) { const float(*co)[3] = vertCos; @@ -2345,7 +2303,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); @@ -2405,7 +2363,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); } @@ -2469,6 +2427,7 @@ float (*BKE_keyblock_convert_to_vertcos(Object *ob, KeyBlock *kb))[3] } /************************* raw coord offsets ************************/ + void BKE_keyblock_update_from_offset(Object *ob, KeyBlock *kb, const float (*ofs)[3]) { int a; @@ -2506,15 +2465,6 @@ void BKE_keyblock_update_from_offset(Object *ob, KeyBlock *kb, const float (*ofs /* ==========================================================*/ -/** - * Move shape key from org_index to new_index. Safe, clamps index to valid range, - * updates reference keys, the object's active shape index, - * the 'frame' value in case of absolute keys, etc. - * Note indices are expected in real values (not 'fake' shapenr +1 ones). - * - * \param org_index: if < 0, current object's active shape will be used as skey to move. - * \return true if something was done, else false. - */ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) { Key *key = BKE_key_from_object(ob); @@ -2593,9 +2543,6 @@ bool BKE_keyblock_move(Object *ob, int org_index, int new_index) return true; } -/** - * Check if given keyblock (as index) is used as basis by others in given key. - */ bool BKE_keyblock_is_basis(Key *key, const int index) { KeyBlock *kb; diff --git a/source/blender/blenkernel/intern/keyconfig.c b/source/blender/blenkernel/intern/keyconfig.c index d25f475c140..84e11c1166e 100644 --- a/source/blender/blenkernel/intern/keyconfig.c +++ b/source/blender/blenkernel/intern/keyconfig.c @@ -121,7 +121,6 @@ void BKE_keyconfig_pref_type_free(void) /** \name Key-Config Versioning * \{ */ -/* Set select mouse, for versioning code. */ void BKE_keyconfig_pref_set_select_mouse(UserDef *userdef, int value, bool override) { wmKeyConfigPref *kpt = BKE_keyconfig_pref_ensure(userdef, WM_KEYCONFIG_STR_DEFAULT); @@ -201,10 +200,6 @@ void BKE_keyconfig_keymap_filter_item(wmKeyMap *keymap, } } -/** - * Filter & optionally remove key-map items, - * intended for versioning, but may be used in other situations too. - */ void BKE_keyconfig_pref_filter_items(struct UserDef *userdef, const struct wmKeyConfigFilterItemParams *params, bool (*filter_fn)(wmKeyMapItem *kmi, void *user_data), diff --git a/source/blender/blenkernel/intern/lattice.c b/source/blender/blenkernel/intern/lattice.c index 9bca8172e64..2f5c5d0a0d5 100644 --- a/source/blender/blenkernel/intern/lattice.c +++ b/source/blender/blenkernel/intern/lattice.c @@ -131,7 +131,7 @@ 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) @@ -197,6 +197,7 @@ IDTypeInfo IDType_ID_LT = { .name_plural = "lattices", .translation_context = BLT_I18NCONTEXT_ID_LATTICE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = lattice_init_data, .copy_data = lattice_copy_data, @@ -204,6 +205,7 @@ IDTypeInfo IDType_ID_LT = { .make_local = NULL, .foreach_id = lattice_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = lattice_blend_write, @@ -464,7 +466,7 @@ void outside_lattice(Lattice *lt) bp->hide = 1; bp->f1 &= ~SELECT; - /* u extrema */ + /* U extrema. */ bp1 = latt_bp(lt, 0, v, w); bp2 = latt_bp(lt, lt->pntsu - 1, v, w); @@ -473,7 +475,7 @@ void outside_lattice(Lattice *lt) bp->vec[1] = (1.0f - fac1) * bp1->vec[1] + fac1 * bp2->vec[1]; bp->vec[2] = (1.0f - fac1) * bp1->vec[2] + fac1 * bp2->vec[2]; - /* v extrema */ + /* V extrema. */ bp1 = latt_bp(lt, u, 0, w); bp2 = latt_bp(lt, u, lt->pntsv - 1, w); @@ -482,7 +484,7 @@ void outside_lattice(Lattice *lt) bp->vec[1] += (1.0f - fac1) * bp1->vec[1] + fac1 * bp2->vec[1]; bp->vec[2] += (1.0f - fac1) * bp1->vec[2] + fac1 * bp2->vec[2]; - /* w extrema */ + /* W extrema. */ bp1 = latt_bp(lt, u, v, 0); bp2 = latt_bp(lt, u, v, lt->pntsw - 1); diff --git a/source/blender/blenkernel/intern/lattice_deform.c b/source/blender/blenkernel/intern/lattice_deform.c index f9437eeaffa..af721412472 100644 --- a/source/blender/blenkernel/intern/lattice_deform.c +++ b/source/blender/blenkernel/intern/lattice_deform.c @@ -115,7 +115,7 @@ LatticeDeformData *BKE_lattice_deform_data_create(const Object *oblatt, const Ob defgrp_index = BKE_id_defgroup_name_index(<->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); } 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 434a2296d95..a59dd6f2e0e 100644 --- a/source/blender/blenkernel/intern/layer.c +++ b/source/blender/blenkernel/intern/layer.c @@ -77,7 +77,9 @@ static const short g_base_collection_flags = (BASE_VISIBLE_DEPSGRAPH | BASE_VISI /* prototype */ static void object_bases_iterator_next(BLI_Iterator *iter, const int flag); -/*********************** Layer Collections and bases *************************/ +/* -------------------------------------------------------------------- */ +/** \name Layer Collections and Bases + * \{ */ static LayerCollection *layer_collection_add(ListBase *lb_parent, Collection *collection) { @@ -113,12 +115,14 @@ static Base *object_base_new(Object *ob) return base; } -/********************************* View Layer ********************************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name View Layer + * \{ */ /* RenderLayer */ -/* Returns the default view layer to view in workspaces if there is - * none linked to the workspace yet. */ ViewLayer *BKE_view_layer_default_view(const Scene *scene) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -131,7 +135,6 @@ ViewLayer *BKE_view_layer_default_view(const Scene *scene) return scene->view_layers.first; } -/* Returns the default view layer to render if we need to render just one. */ ViewLayer *BKE_view_layer_default_render(const Scene *scene) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -144,7 +147,6 @@ ViewLayer *BKE_view_layer_default_render(const Scene *scene) return scene->view_layers.first; } -/* Returns view layer with matching name, or NULL if not found. */ ViewLayer *BKE_view_layer_find(const Scene *scene, const char *layer_name) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -156,11 +158,6 @@ ViewLayer *BKE_view_layer_find(const Scene *scene, const char *layer_name) return NULL; } -/** - * This is a placeholder to know which areas of the code need to be addressed - * for the Workspace changes. Never use this, you should typically get the - * active layer from the context or window. - */ ViewLayer *BKE_view_layer_context_active_PLACEHOLDER(const Scene *scene) { BLI_assert(scene->view_layers.first); @@ -170,7 +167,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"); @@ -183,6 +180,7 @@ static ViewLayer *view_layer_add(const char *name) 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; @@ -197,10 +195,6 @@ static void layer_collection_exclude_all(LayerCollection *layer_collection) } } -/** - * Add a new view layer - * by default, a view layer has the master collection - */ ViewLayer *BKE_view_layer_add(Scene *scene, const char *name, ViewLayer *view_layer_source, @@ -248,7 +242,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)); @@ -260,9 +254,6 @@ void BKE_view_layer_free(ViewLayer *view_layer) BKE_view_layer_free_ex(view_layer, true); } -/** - * Free (or release) any data used by this ViewLayer. - */ void BKE_view_layer_free_ex(ViewLayer *view_layer, const bool do_id_user) { view_layer->basact = NULL; @@ -303,9 +294,6 @@ void BKE_view_layer_free_ex(ViewLayer *view_layer, const bool do_id_user) MEM_freeN(view_layer); } -/** - * Tag all the selected objects of a render-layer. - */ void BKE_view_layer_selected_objects_tag(ViewLayer *view_layer, const int tag) { LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { @@ -331,13 +319,6 @@ static bool find_scene_collection_in_scene_collections(ListBase *lb, const Layer return false; } -/** - * Fallback for when a Scene has no camera to use - * - * \param view_layer: in general you want to use the same ViewLayer that is used - * for depsgraph. If rendering you pass the scene active layer, when viewing in the viewport - * you want to get ViewLayer from context. - */ Object *BKE_view_layer_camera_find(ViewLayer *view_layer) { LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { @@ -349,9 +330,6 @@ Object *BKE_view_layer_camera_find(ViewLayer *view_layer) return NULL; } -/** - * Find the ViewLayer a LayerCollection belongs to - */ ViewLayer *BKE_view_layer_find_from_collection(const Scene *scene, LayerCollection *lc) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -365,7 +343,7 @@ ViewLayer *BKE_view_layer_find_from_collection(const Scene *scene, LayerCollecti /* Base */ -static void view_layer_bases_hash_create(ViewLayer *view_layer) +static void view_layer_bases_hash_create(ViewLayer *view_layer, const bool do_base_duplicates_fix) { static ThreadMutex hash_lock = BLI_MUTEX_INITIALIZER; @@ -375,15 +353,29 @@ static void view_layer_bases_hash_create(ViewLayer *view_layer) if (view_layer->object_bases_hash == NULL) { GHash *hash = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); - LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) { + LISTBASE_FOREACH_MUTABLE (Base *, base, &view_layer->object_bases) { if (base->object) { - /* Some processes, like ID remapping, may lead to having several bases with the same - * object. So just take the first one here, and ignore all others - * (#BKE_layer_collection_sync will clean this up anyway). */ void **val_pp; if (!BLI_ghash_ensure_p(hash, base->object, &val_pp)) { *val_pp = base; } + /* The same object has several bases. + * + * In normal cases this is a serious bug, but this is a common situation when remapping + * an object into another one already present in the same View Layer. While ideally we + * would process this case separately, for performances reasons it makes more sense to + * tackle it here. */ + else if (do_base_duplicates_fix) { + if (view_layer->basact == base) { + view_layer->basact = NULL; + } + BLI_freelinkN(&view_layer->object_bases, base); + } + else { + CLOG_FATAL(&LOG, + "Object '%s' has more than one entry in view layer's object bases listbase", + base->object->id.name + 2); + } } } @@ -398,7 +390,7 @@ static void view_layer_bases_hash_create(ViewLayer *view_layer) Base *BKE_view_layer_base_find(ViewLayer *view_layer, Object *ob) { if (!view_layer->object_bases_hash) { - view_layer_bases_hash_create(view_layer); + view_layer_bases_hash_create(view_layer, false); } return BLI_ghash_lookup(view_layer->object_bases_hash, ob); @@ -421,7 +413,12 @@ void BKE_view_layer_base_select_and_set_active(struct ViewLayer *view_layer, Bas } } -/**************************** Copy View Layer and Layer Collections ***********************/ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy View Layer and Layer Collections + * \{ */ + static void layer_aov_copy_data(ViewLayer *view_layer_dst, const ViewLayer *view_layer_src, ListBase *aovs_dst, @@ -470,11 +467,6 @@ static void layer_collections_copy_data(ViewLayer *view_layer_dst, } } -/** - * Only copy internal data of ViewLayer from source to already allocated/initialized destination. - * - * \param flag: Copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more). - */ void BKE_view_layer_copy_data(Scene *scene_dst, const Scene *UNUSED(scene_src), ViewLayer *view_layer_dst, @@ -619,26 +611,17 @@ static bool layer_collection_hidden(ViewLayer *view_layer, LayerCollection *lc) return false; } -/** - * Get the collection for a given index - */ LayerCollection *BKE_layer_collection_from_index(ViewLayer *view_layer, const int index) { int i = 0; return collection_from_index(&view_layer->layer_collections, index, &i); } -/** - * Get the active collection - */ LayerCollection *BKE_layer_collection_get_active(ViewLayer *view_layer) { return view_layer->active_collection; } -/* - * Activate collection - */ bool BKE_layer_collection_activate(ViewLayer *view_layer, LayerCollection *lc) { if (lc->flag & LAYER_COLLECTION_EXCLUDE) { @@ -649,9 +632,6 @@ bool BKE_layer_collection_activate(ViewLayer *view_layer, LayerCollection *lc) return true; } -/** - * Activate first parent collection - */ LayerCollection *BKE_layer_collection_activate_parent(ViewLayer *view_layer, LayerCollection *lc) { CollectionParent *parent = lc->collection->parents.first; @@ -689,10 +669,6 @@ static int collection_count(const ListBase *lb) return i; } -/** - * Get the total number of collections - * (including all the nested collections) - */ int BKE_layer_collection_count(const ViewLayer *view_layer) { return collection_count(&view_layer->layer_collections); @@ -720,16 +696,16 @@ static int index_from_collection(ListBase *lb, const LayerCollection *lc, int *i return -1; } -/** - * Return -1 if not found - */ int BKE_layer_collection_findindex(ViewLayer *view_layer, const LayerCollection *lc) { int i = 0; return index_from_collection(&view_layer->layer_collections, lc, &i); } -/*********************************** Syncing ********************************* +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Syncing * * The layer collection tree mirrors the scene collection tree. Whenever that * changes we need to synchronize them so that there is a corresponding layer @@ -739,9 +715,9 @@ int BKE_layer_collection_findindex(ViewLayer *view_layer, const LayerCollection * * The view layer also contains a list of bases for each object that exists * in at least one layer collection. That list is also synchronized here, and - * stores state like selection. */ - -/* This API allows to temporarily forbid resync of LayerCollections. + * stores state like selection. + * + * 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). @@ -750,19 +726,20 @@ int BKE_layer_collection_findindex(ViewLayer *view_layer, const LayerCollection * 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. + * \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 + * \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 + * \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) @@ -1219,11 +1196,23 @@ static bool view_layer_objects_base_cache_validate(ViewLayer *UNUSED(view_layer) } #endif -/** - * Update view layer collection tree from collections used in the scene. - * This is used when collections are removed or added, both while editing - * and on file loaded in case linked data changed or went missing. - */ +void BKE_layer_collection_doversion_2_80(const Scene *scene, ViewLayer *view_layer) +{ + LayerCollection *first_layer_collection = view_layer->layer_collections.first; + if (BLI_listbase_count_at_most(&view_layer->layer_collections, 2) > 1 || + first_layer_collection->collection != scene->master_collection) { + /* In some cases (from older files) we do have a master collection, but no matching layer, + * instead all the children of the master collection have their layer collections in the + * viewlayer's list. This is not a valid situation, add a layer for the master collection and + * add all existing first-level layers as children of that new master layer. */ + ListBase layer_collections = view_layer->layer_collections; + BLI_listbase_clear(&view_layer->layer_collections); + LayerCollection *master_layer_collection = layer_collection_add(&view_layer->layer_collections, + scene->master_collection); + master_layer_collection->layer_collections = layer_collections; + } +} + void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer) { if (no_resync) { @@ -1235,18 +1224,32 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer) 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)) { + /* In some cases (from older files, or when creating a new ViewLayer from + * #BKE_view_layer_add), 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. */ layer_collection_add(&view_layer->layer_collections, scene->master_collection); } +#ifndef NDEBUG + { + BLI_assert_msg(BLI_listbase_count_at_most(&view_layer->layer_collections, 2) == 1, + "ViewLayer's first level of children layer collections should always have " + "exactly one item"); + + LayerCollection *first_layer_collection = view_layer->layer_collections.first; + BLI_assert_msg(first_layer_collection->collection == scene->master_collection, + "ViewLayer's first layer collection should always be the one for the scene's " + "master collection"); + } +#endif + /* Free cache. */ MEM_SAFE_FREE(view_layer->object_bases_array); /* Create object to base hash if it does not exist yet. */ if (!view_layer->object_bases_hash) { - view_layer_bases_hash_create(view_layer); + view_layer_bases_hash_create(view_layer, false); } /* Clear visible and selectable flags to be reset. */ @@ -1359,6 +1362,11 @@ void BKE_main_collection_sync_remap(const Main *bmain) if (view_layer->object_bases_hash) { BLI_ghash_free(view_layer->object_bases_hash, NULL, NULL); view_layer->object_bases_hash = NULL; + + /* Directly re-create the mapping here, so that we can also deal with duplicates in + * `view_layer->object_bases` list of bases properly. This is the only place where such + * duplicates should be fixed, and not considered as a critical error. */ + view_layer_bases_hash_create(view_layer, true); } } @@ -1376,14 +1384,12 @@ void BKE_main_collection_sync_remap(const Main *bmain) BKE_main_collection_sync(bmain); } -/* ---------------------------------------------------------------------- */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Selection + * \{ */ -/** - * Select all the objects of this layer collection - * - * It also select the objects that are in nested collections. - * \note Recursive - */ bool BKE_layer_collection_objects_select(ViewLayer *view_layer, LayerCollection *lc, bool deselect) { if (lc->collection->flag & COLLECTION_HIDE_SELECT) { @@ -1460,9 +1466,12 @@ bool BKE_layer_collection_has_layer_collection(LayerCollection *lc_parent, return false; } -/* ---------------------------------------------------------------------- */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Visibility + * \{ */ -/* Update after toggling visibility of an object base. */ void BKE_base_set_visible(Scene *scene, ViewLayer *view_layer, Base *base, bool extend) { if (!extend) { @@ -1535,6 +1544,12 @@ bool BKE_object_is_visible_in_viewport(const View3D *v3d, const struct Object *o return true; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Collection Isolation & Local View + * \{ */ + static void layer_collection_flag_set_recursive(LayerCollection *lc, const int flag) { lc->flag |= flag; @@ -1551,14 +1566,6 @@ static void layer_collection_flag_unset_recursive(LayerCollection *lc, const int } } -/** - * Isolate the collection - hide all other collections but this one. - * Make sure to show all the direct parents and all children of the layer collection as well. - * When extending we simply show the collections and its direct family. - * - * If the collection or any of its parents is disabled, make it enabled. - * Don't change the children disable state though. - */ void BKE_layer_collection_isolate_global(Scene *scene, ViewLayer *view_layer, LayerCollection *lc, @@ -1667,9 +1674,6 @@ void BKE_layer_collection_local_sync(ViewLayer *view_layer, const View3D *v3d) } } -/** - * Sync the local collection for all the 3D Viewports. - */ void BKE_layer_collection_local_sync_all(const Main *bmain) { if (no_resync) { @@ -1693,11 +1697,6 @@ void BKE_layer_collection_local_sync_all(const Main *bmain) } } -/** - * Isolate the collection locally - * - * Same as BKE_layer_collection_isolate_local but for a viewport - */ void BKE_layer_collection_isolate_local(ViewLayer *view_layer, const View3D *v3d, LayerCollection *lc, @@ -1770,11 +1769,6 @@ static void layer_collection_bases_hide_recursive(ViewLayer *view_layer, LayerCo } } -/** - * Hide/show all the elements of a collection. - * Don't change the collection children enable/disable state, - * but it may change it for the collection itself. - */ void BKE_layer_collection_set_visible(ViewLayer *view_layer, LayerCollection *lc, const bool visible, @@ -1861,9 +1855,6 @@ static LayerCollection *find_layer_collection_by_scene_collection(LayerCollectio return NULL; } -/** - * Return the first matching LayerCollection in the ViewLayer for the Collection. - */ LayerCollection *BKE_layer_collection_first_from_scene_collection(const ViewLayer *view_layer, const Collection *collection) { @@ -1877,17 +1868,11 @@ LayerCollection *BKE_layer_collection_first_from_scene_collection(const ViewLaye return NULL; } -/** - * See if view layer has the scene collection linked directly, or indirectly (nested) - */ 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; } -/** - * See if the object is in any of the scene layers of the scene - */ bool BKE_scene_has_object(Scene *scene, Object *ob) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -1998,6 +1983,8 @@ static void objects_iterator_end(BLI_Iterator *iter) object_bases_iterator_end(iter); } +/** \} */ + /* -------------------------------------------------------------------- */ /** \name BKE_view_layer_selected_objects_iterator * See: #FOREACH_SELECTED_OBJECT_BEGIN @@ -2187,11 +2174,10 @@ void BKE_view_layer_bases_in_mode_iterator_end(BLI_Iterator *UNUSED(iter)) /** \} */ -/* Evaluation. */ +/* -------------------------------------------------------------------- */ +/** \name Evaluation + * \{ */ -/* Applies object's restrict flags on top of flags coming from the collection - * and stores those in base->flag. BASE_VISIBLE_DEPSGRAPH ignores viewport flags visibility - * (i.e., restriction and local collection). */ void BKE_base_eval_flags(Base *base) { /* Apply collection flags. */ @@ -2250,6 +2236,12 @@ void BKE_layer_eval_view_layer_indexed(struct Depsgraph *depsgraph, layer_eval_view_layer(depsgraph, scene, view_layer); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Blend File I/O + * \{ */ + static void write_layer_collections(BlendWriter *writer, ListBase *lb) { LISTBASE_FOREACH (LayerCollection *, lc, lb) { @@ -2370,6 +2362,8 @@ void BKE_view_layer_blend_read_lib(BlendLibReader *reader, Library *lib, ViewLay IDP_BlendReadLib(reader, view_layer->id_properties); } +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Shader AOV * \{ */ @@ -2380,8 +2374,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) @@ -2450,12 +2448,6 @@ static void bke_view_layer_verify_aov_cb(void *userdata, } } -/* Update the naming and conflicts of the AOVs. - * - * Name must be unique between all AOVs. - * Conflicts with render passes will show a conflict icon. Reason is that switching a render - * engine or activating a render pass could lead to other conflicts that wouldn't be that clear - * for the user. */ void BKE_view_layer_verify_aov(struct RenderEngine *engine, struct Scene *scene, struct ViewLayer *view_layer) @@ -2473,7 +2465,6 @@ void BKE_view_layer_verify_aov(struct RenderEngine *engine, BLI_ghash_free(name_count, MEM_freeN, NULL); } -/* Check if the given view layer has at least one valid AOV. */ bool BKE_view_layer_has_valid_aov(ViewLayer *view_layer) { LISTBASE_FOREACH (ViewLayerAOV *, aov, &view_layer->aovs) { diff --git a/source/blender/blenkernel/intern/layer_test.cc b/source/blender/blenkernel/intern/layer_test.cc index c918c0cab67..c8e5de75bfa 100644 --- a/source/blender/blenkernel/intern/layer_test.cc +++ b/source/blender/blenkernel/intern/layer_test.cc @@ -33,6 +33,8 @@ #include "RNA_access.h" +#include "GHOST_Path-api.h" + namespace blender::bke::tests { TEST(view_layer, aov_unique_names) @@ -69,7 +71,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 +80,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); @@ -94,6 +96,7 @@ TEST(view_layer, aov_unique_names) IMB_exit(); BKE_appdir_exit(); CLG_exit(); + GHOST_DisposeSystemPaths(); } static void test_render_pass_conflict(Scene *scene, @@ -173,6 +176,7 @@ TEST(view_layer, aov_conflict) IMB_exit(); BKE_appdir_exit(); CLG_exit(); + GHOST_DisposeSystemPaths(); } } // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/layer_utils.c b/source/blender/blenkernel/intern/layer_utils.c index 48179e0c3bf..3760fe8a976 100644 --- a/source/blender/blenkernel/intern/layer_utils.c +++ b/source/blender/blenkernel/intern/layer_utils.c @@ -193,13 +193,6 @@ bool BKE_view_layer_filter_edit_mesh_has_edges(const Object *ob, void *UNUSED(us return false; } -/** - * Use this in rare cases we need to detect a pair of objects (active, selected). - * This returns the other non-active selected object. - * - * Returns NULL with it finds multiple other selected objects - * as behavior in this case would be random from the user perspective. - */ Object *BKE_view_layer_non_active_selected_object(struct ViewLayer *view_layer, const struct View3D *v3d) { @@ -221,4 +214,5 @@ Object *BKE_view_layer_non_active_selected_object(struct ViewLayer *view_layer, FOREACH_SELECTED_OBJECT_END; return ob_result; } + /** \} */ diff --git a/source/blender/blenkernel/intern/lib_id.c b/source/blender/blenkernel/intern/lib_id.c index 18824e73ee5..49a518607f1 100644 --- a/source/blender/blenkernel/intern/lib_id.c +++ b/source/blender/blenkernel/intern/lib_id.c @@ -89,7 +89,6 @@ static CLG_LogRef LOG = {.identifier = "bke.lib_id"}; -/* Empty shell mostly, but needed for read code. */ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { .id_code = ID_LINK_PLACEHOLDER, .id_filter = 0, @@ -99,6 +98,7 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { .name_plural = "link_placeholders", .translation_context = BLT_I18NCONTEXT_ID_ID, .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING, + .asset_type_info = NULL, .init_data = NULL, .copy_data = NULL, @@ -106,6 +106,8 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = NULL, + .owner_get = NULL, .blend_write = NULL, .blend_read_data = NULL, @@ -124,18 +126,56 @@ IDTypeInfo IDType_ID_LINK_PLACEHOLDER = { /* ************* general ************************ */ /** + * Rewrites a relative path to be relative to the main file - unless the path is + * absolute, in which case it is not altered. + */ +static bool lib_id_library_local_paths_callback(BPathForeachPathData *bpath_data, + char *r_path_dst, + const char *path_src) +{ + const char **data = bpath_data->user_data; + /* be sure there is low chance of the path being too short */ + char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE]; + const char *base_new = data[0]; + const char *base_old = data[1]; + + if (BLI_path_is_rel(base_old)) { + CLOG_ERROR(&LOG, "old base path '%s' is not absolute.", base_old); + return false; + } + + /* Make referenced file absolute. This would be a side-effect of + * BLI_path_normalize, but we do it explicitly so we know if it changed. */ + BLI_strncpy(filepath, path_src, FILE_MAX); + if (BLI_path_abs(filepath, base_old)) { + /* Path was relative and is now absolute. Remap. + * Important BLI_path_normalize runs before the path is made relative + * because it won't work for paths that start with "//../" */ + BLI_path_normalize(base_new, filepath); + BLI_path_rel(filepath, base_new); + BLI_strncpy(r_path_dst, filepath, FILE_MAX); + return true; + } + + /* Path was not relative to begin with. */ + return false; +} + +/** * This has to be called from each make_local_* func, we could call from BKE_lib_id_make_local() * but then the make local functions would not be self contained. * Also note that the id _must_ have a library - campbell */ +/* TODO: This can probably be replaced by an ID-level version of #BKE_bpath_relative_rebase. */ static void lib_id_library_local_paths(Main *bmain, Library *lib, ID *id) { const char *bpath_user_data[2] = {BKE_main_blendfile_path(bmain), lib->filepath_abs}; - BKE_bpath_traverse_id(bmain, - id, - BKE_bpath_relocate_visitor, - BKE_BPATH_TRAVERSE_SKIP_MULTIFILE, - (void *)bpath_user_data); + BKE_bpath_foreach_path_id( + &(BPathForeachPathData){.bmain = bmain, + .callback_function = lib_id_library_local_paths_callback, + .flag = BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE, + .user_data = (void *)bpath_user_data}, + id); } static int lib_id_clear_library_data_users_update_cb(LibraryIDLinkCallbackData *cb_data) @@ -149,11 +189,7 @@ static int lib_id_clear_library_data_users_update_cb(LibraryIDLinkCallbackData * return IDWALK_RET_NOP; } -/** - * Pull an ID out of a library (make it local). Only call this for IDs that - * don't have other library users. - */ -void BKE_lib_id_clear_library_data(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; @@ -177,6 +213,16 @@ void BKE_lib_id_clear_library_data(Main *bmain, ID *id) BKE_lib_libblock_session_uuid_renew(id); } + if (ID_IS_ASSET(id)) { + if ((flags & LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR) != 0) { + BKE_asset_metadata_free(&id->asset_data); + } + else { + /* Assets should always have a fake user. Ensure this is the case after "Make Local". */ + id_fake_user_set(id); + } + } + /* 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. */ @@ -193,7 +239,7 @@ void BKE_lib_id_clear_library_data(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) { - BKE_lib_id_clear_library_data(bmain, &key->id); + BKE_lib_id_clear_library_data(bmain, &key->id, flags); } DEG_relations_tag_update(bmain); @@ -222,14 +268,6 @@ void id_lib_indirect_weak_link(ID *id) } } -/** - * Ensure we have a real user - * - * \note Now that we have flags, we could get rid of the 'fake_user' special case, - * flags are enough to ensure we always have a real user. - * However, #ID_REAL_USERS is used in several places outside of core lib.c, - * so think we can wait later to make this change. - */ void id_us_ensure_real(ID *id) { if (id) { @@ -260,10 +298,6 @@ void id_us_clear_real(ID *id) } } -/** - * Same as \a id_us_plus, but does not handle lib indirect -> extern. - * Only used by readfile.c so far, but simpler/safer to keep it here nonetheless. - */ void id_us_plus_no_lib(ID *id) { if (id) { @@ -288,7 +322,6 @@ void id_us_plus(ID *id) } } -/* decrements the user count for *id. */ void id_us_min(ID *id) { if (id) { @@ -372,6 +405,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. */ @@ -386,7 +420,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); - BKE_lib_id_clear_library_data(bmain, *id_pointer); + BKE_lib_id_clear_library_data(bmain, *id_pointer, flags); } return IDWALK_RET_NOP; } @@ -403,64 +437,77 @@ static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data) return IDWALK_RET_NOP; } -/** - * 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); } } -/** - * Generic 'make local' function, works for most of data-block types... - */ -void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) +void BKE_lib_id_make_local_generic_action_define( + struct Main *bmain, struct ID *id, int flags, bool *r_force_local, bool *r_force_copy) { - 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); + if (force_local || force_copy) { + /* Already set by caller code, nothing to do here. */ + *r_force_local = force_local; + *r_force_copy = force_copy; + return; + } + + const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; bool is_local = false, is_lib = false; - /* - only lib users: do nothing (unless force_local is set) - * - only local users: set flag + /* - no user (neither lib nor local): make local (happens e.g. with UI-used only data). + * - only lib users: do nothing (unless force_local is set) + * - only local users: make local * - mixed: make copy * In case we make a whole lib's content local, * we always want to localize, and we skip remapping (done later). */ - 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 (!lib_local && !is_local && !is_lib) { + force_local = true; + } + else if (lib_local || is_local) { + if (!is_lib) { + force_local = true; + } + else { + force_copy = true; } } + *r_force_local = force_local; + *r_force_copy = force_copy; +} + +void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) +{ + if (!ID_IS_LINKED(id)) { + return; + } + + bool force_local, force_copy; + BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy); + if (force_local) { - BKE_lib_id_clear_library_data(bmain, id); - BKE_lib_id_expand_local(bmain, id); + 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); @@ -488,6 +535,7 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) } } + const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; if (!lib_local) { BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE); } @@ -495,15 +543,6 @@ void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags) } } -/** - * Calls the appropriate make_local method for the block, unless test is set. - * - * \note Always set #ID.newid pointer in case it gets duplicated. - * - * \param flags: Special flag used when making a whole library's content local, - * it needs specific handling. - * \return true is the ID has successfully been made local. - */ bool BKE_lib_id_make_local(Main *bmain, ID *id, const int flags) { const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0; @@ -578,27 +617,6 @@ bool BKE_id_copy_is_allowed(const ID *id) #undef LIB_ID_TYPES_NOCOPY } -/** - * Generic entry point for copying a data-block (new API). - * - * \note Copy is generally only affecting the given data-block - * (no ID used by copied one will be affected, besides usercount). - * There are exceptions though: - * - Embedded IDs (root node trees and master collections) are always copied with their owner. - * - If #LIB_ID_COPY_ACTIONS is defined, actions used by animdata will be duplicated. - * - If #LIB_ID_COPY_SHAPEKEY is defined, shapekeys will be duplicated. - * - If #LIB_ID_CREATE_LOCAL is defined, root node trees will be deep-duplicated recursively. - * - * \note Usercount of new copy is always set to 1. - * - * \param bmain: Main database, may be NULL only if LIB_ID_CREATE_NO_MAIN is specified. - * \param id: Source data-block. - * \param r_newid: Pointer to new (copied) ID pointer, may be NULL. Used to allow copying into - * already allocated memory. - * \param flag: Set of copy options, see DNA_ID.h enum for details (leave to zero for default, - * full copy). - * \return NULL when copying that ID type is not supported, the new copy otherwise. - */ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag) { ID *newid = (r_newid != NULL) ? *r_newid : NULL; @@ -648,7 +666,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; @@ -661,20 +679,15 @@ ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag) return newid; } -/** - * 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(Main *bmain, const ID *id) { return BKE_id_copy_ex(bmain, id, NULL, LIB_ID_COPY_DEFAULT); } -/** - * 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; @@ -685,7 +698,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); @@ -751,31 +764,16 @@ static void id_swap(Main *bmain, ID *id_a, ID *id_b, const bool do_full_id) } } -/** - * Does a mere memory swap over the whole IDs data (including type-specific memory). - * \note Most internal ID data itself is not swapped (only IDProperties are). - * - * \param bmain: May be NULL, in which case there will be no remapping of internal pointers to - * itself. - */ void BKE_lib_id_swap(Main *bmain, ID *id_a, ID *id_b) { id_swap(bmain, id_a, id_b, false); } -/** - * Does a mere memory swap over the whole IDs data (including type-specific memory). - * \note All internal ID data itself is also swapped. - * - * \param bmain: May be NULL, in which case there will be no remapping of internal pointers to - * itself. - */ void BKE_lib_id_swap_full(Main *bmain, ID *id_a, ID *id_b) { id_swap(bmain, id_a, id_b, true); } -/** Does *not* set ID->newid pointer. */ bool id_single_user(bContext *C, ID *id, PointerRNA *ptr, PropertyRNA *prop) { ID *newid = NULL; @@ -840,7 +838,6 @@ static int libblock_management_us_min(LibraryIDLinkCallbackData *cb_data) return IDWALK_RET_NOP; } -/** Add a 'NO_MAIN' data-block to given main (also sets usercounts of its IDs if needed). */ void BKE_libblock_management_main_add(Main *bmain, void *idv) { ID *id = idv; @@ -874,7 +871,6 @@ void BKE_libblock_management_main_add(Main *bmain, void *idv) BKE_lib_libblock_session_uuid_ensure(id); } -/** Remove a data-block from given main (set it to 'NO_MAIN' status). */ void BKE_libblock_management_main_remove(Main *bmain, void *idv) { ID *id = idv; @@ -919,9 +915,6 @@ void BKE_libblock_management_usercounts_clear(Main *bmain, void *idv) id->tag |= LIB_TAG_NO_USER_REFCOUNT; } -/** - * Clear or set given tags for all ids in listbase (runtime tags). - */ void BKE_main_id_tag_listbase(ListBase *lb, const int tag, const bool value) { ID *id; @@ -938,9 +931,6 @@ void BKE_main_id_tag_listbase(ListBase *lb, const int tag, const bool value) } } -/** - * Clear or set given tags for all ids of given type in bmain (runtime tags). - */ void BKE_main_id_tag_idcode(struct Main *mainvar, const short type, const int tag, @@ -951,9 +941,6 @@ void BKE_main_id_tag_idcode(struct Main *mainvar, BKE_main_id_tag_listbase(lb, tag, value); } -/** - * Clear or set given tags for all ids in bmain (runtime tags). - */ void BKE_main_id_tag_all(struct Main *mainvar, const int tag, const bool value) { ListBase *lbarray[INDEX_ID_MAX]; @@ -965,9 +952,6 @@ void BKE_main_id_tag_all(struct Main *mainvar, const int tag, const bool value) } } -/** - * Clear or set given flags for all ids in listbase (persistent flags). - */ void BKE_main_id_flag_listbase(ListBase *lb, const int flag, const bool value) { ID *id; @@ -984,9 +968,6 @@ void BKE_main_id_flag_listbase(ListBase *lb, const int flag, const bool value) } } -/** - * Clear or set given flags for all ids in bmain (persistent flags). - */ void BKE_main_id_flag_all(Main *bmain, const int flag, const bool value) { ListBase *lbarray[INDEX_ID_MAX]; @@ -1052,9 +1033,6 @@ void BKE_main_lib_objects_recalc_all(Main *bmain) * * **************************** */ -/** - * Get allocation size of a given data-block type and optionally allocation name. - */ size_t BKE_libblock_get_alloc_info(short type, const char **name) { const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(type); @@ -1072,10 +1050,6 @@ size_t BKE_libblock_get_alloc_info(short type, const char **name) return id_type->struct_size; } -/** - * Allocates and returns memory of the right size for the specified block type, - * initialized to zero. - */ void *BKE_libblock_alloc_notest(short type) { const char *name; @@ -1087,12 +1061,6 @@ void *BKE_libblock_alloc_notest(short type) return NULL; } -/** - * Allocates and returns a block of the specified type, with the specified name - * (adjusted as necessary to ensure uniqueness), and appended to the specified list. - * The user count is set to 1, all other content (apart from name and links) being - * initialized to zero. - */ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int flag) { BLI_assert((flag & LIB_ID_CREATE_NO_ALLOCATE) == 0); @@ -1118,8 +1086,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); @@ -1129,6 +1098,11 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl /* alphabetic insertion: is in new_id */ BKE_main_unlock(bmain); + /* 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); @@ -1149,10 +1123,6 @@ void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int fl return id; } -/** - * Initialize an ID of given type, such that it has valid 'empty' data. - * ID is assumed to be just calloc'ed. - */ void BKE_libblock_init_empty(ID *id) { const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id); @@ -1170,12 +1140,6 @@ void BKE_libblock_init_empty(ID *id) /* ********** ID session-wise UUID management. ********** */ static uint global_session_uuid = 0; -/** - * Generate a session-wise uuid for the given \a id. - * - * \note "session-wise" here means while editing a given .blend file. Once a new .blend file is - * loaded or created, undo history is cleared/reset, and so is the uuid counter. - */ void BKE_lib_libblock_session_uuid_ensure(ID *id) { if (id->session_uuid == MAIN_ID_SESSION_UUID_UNSET) { @@ -1189,25 +1153,12 @@ void BKE_lib_libblock_session_uuid_ensure(ID *id) } } -/** - * Re-generate a new session-wise uuid for the given \a id. - * - * \warning This has a few very specific use-cases, no other usage is expected currently: - * - To handle UI-related data-blocks that are kept across new file reading, when we do keep - * existing UI. - * - For IDs that are made local without needing any copying. - */ void BKE_lib_libblock_session_uuid_renew(ID *id) { id->session_uuid = MAIN_ID_SESSION_UUID_UNSET; BKE_lib_libblock_session_uuid_ensure(id); } -/** - * Generic helper to create a new empty data-block of given type in given \a bmain database. - * - * \param name: can be NULL, in which case we get default name for this ID type. - */ void *BKE_id_new(Main *bmain, const short type, const char *name) { BLI_assert(bmain != NULL); @@ -1222,11 +1173,6 @@ void *BKE_id_new(Main *bmain, const short type, const char *name) return id; } -/** - * Generic helper to create a new temporary empty data-block of given type, - * *outside* of any Main database. - * - * \param name: can be NULL, in which case we get default name for this ID type. */ void *BKE_id_new_nomain(const short type, const char *name) { if (name == NULL) { @@ -1340,7 +1286,6 @@ void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int ori *r_newid = new_id; } -/* used everywhere in blenkernel */ void *BKE_libblock_copy(Main *bmain, const ID *id) { ID *idn; @@ -1351,6 +1296,7 @@ void *BKE_libblock_copy(Main *bmain, const ID *id) } /* ***************** ID ************************ */ + ID *BKE_libblock_find_name(struct Main *bmain, const short type, const char *name) { ListBase *lb = which_libbase(bmain, type); @@ -1358,14 +1304,20 @@ ID *BKE_libblock_find_name(struct Main *bmain, const short type, const char *nam return BLI_findstring(lb, name, offsetof(ID, name) + 2); } -/** - * Sort given \a id into given \a lb list, using case-insensitive comparison of the id names. - * - * \note All other IDs beside given one are assumed already properly sorted in the list. - * - * \param id_sorting_hint: Ignored if NULL. Otherwise, used to check if we can insert \a id - * immediately before or after that pointer. It must always be into given \a lb list. - */ +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; +} + void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint) { #define ID_SORT_STEP_SIZE 512 @@ -1723,16 +1675,6 @@ static bool check_for_dupid(ListBase *lb, ID *id, char *name, ID **r_id_sorting_ #undef MIN_NUMBER #undef MAX_NUMBER -/** - * Ensures given ID has a unique name in given listbase. - * - * 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 - * (otherwise, just ensure that it is properly sorted). - * - * \return true if a new name had to be created. - */ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const bool do_linked_data) { bool result = false; @@ -1782,7 +1724,6 @@ bool BKE_id_new_name_validate(ListBase *lb, ID *id, const char *tname, const boo return result; } -/* next to indirect usage in read/writefile also in editobject.c scene.c */ void BKE_main_id_newptr_and_tag_clear(Main *bmain) { ID *id; @@ -1905,30 +1846,20 @@ static void library_make_local_copying_check(ID *id, BLI_gset_remove(loop_tags, id, NULL); } -/** - * Make linked data-blocks local. - * - * \param bmain: Almost certainly global main. - * \param lib: If not NULL, only make local data-blocks from this library. - * \param untagged_only: If true, only make local data-blocks not tagged with - * LIB_TAG_PRE_EXISTING. - * \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, * 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 * detect all cases of IDs currently indirectly used, but which will be used by local data only * once this function is finished. This allows to avoid any unneeded duplication of IDs, and - * hence all time lost afterwards to remove orphaned linked data-blocks... - */ + * hence all time lost afterwards to remove orphaned linked data-blocks. */ void BKE_library_make_local(Main *bmain, const Library *lib, GHash *old_to_new_ids, const bool untagged_only, const bool set_fake) { + ListBase *lbarray[INDEX_ID_MAX]; LinkNode *todo_ids = NULL; @@ -2042,8 +1973,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... */ - BKE_lib_id_clear_library_data(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) { @@ -2202,10 +2133,6 @@ void BKE_library_make_local(Main *bmain, #endif } -/** - * Use after setting the ID's name - * When name exists: call 'new_id' - */ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) { ListBase *lb; @@ -2225,9 +2152,6 @@ void BLI_libblock_ensure_unique_name(Main *bmain, const char *name) } } -/** - * Sets the name of a block to name, suitably adjusted for uniqueness. - */ void BKE_libblock_rename(Main *bmain, ID *id, const char *name) { BLI_assert(!ID_IS_LINKED(id)); @@ -2237,16 +2161,6 @@ void BKE_libblock_rename(Main *bmain, ID *id, const char *name) } } -/** - * Generate full name of the data-block (without ID code, but with library if any). - * - * \note Result is unique to a given ID type in a given Main database. - * - * \param name: An allocated string of minimal length #MAX_ID_FULL_NAME, - * will be filled with generated string. - * \param separator_char: Character to use for separating name and library name. Can be 0 to use - * default (' '). - */ void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const ID *id, char separator_char) { strcpy(name, id->name + 2); @@ -2263,19 +2177,6 @@ void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const ID *id, char separa } } -/** - * Generate full name of the data-block (without ID code, but with library if any), - * with a 2 to 3 character prefix prepended indicating whether it comes from a library, - * is overriding, has a fake or no user, etc. - * - * \note Result is unique to a given ID type in a given Main database. - * - * \param name: An allocated string of minimal length #MAX_ID_FULL_NAME_UI, - * will be filled with generated string. - * \param separator_char: Character to use for separating name and library name. Can be 0 to use - * default (' '). - * \param r_prefix_len: The length of the prefix added. - */ void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI], const ID *id, const bool add_lib_hint, @@ -2297,11 +2198,6 @@ void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI], } } -/** - * Generate a concatenation of ID name (including two-chars type code) and its lib name, if any. - * - * \return A unique allocated string key for any ID in the whole Main database. - */ char *BKE_id_to_unique_string_key(const struct ID *id) { if (!ID_IS_LINKED(id)) { @@ -2325,10 +2221,6 @@ void BKE_id_tag_clear_atomic(ID *id, int tag) atomic_fetch_and_and_int32(&id->tag, ~tag); } -/** - * Check that given ID pointer actually is in G_MAIN. - * Main intended use is for debug asserts in places we cannot easily get rid of G_Main... - */ bool BKE_id_is_in_global_main(ID *id) { /* We do not want to fail when id is NULL here, even though this is a bit strange behavior... @@ -2375,10 +2267,6 @@ static int id_order_compare(const void *a, const void *b) return strcmp(id_a->name, id_b->name); } -/** - * Returns ordered list of data-blocks for display in the UI. - * Result is list of LinkData of IDs that must be freed. - */ void BKE_id_ordered_list(ListBase *ordered_lb, const ListBase *lb) { BLI_listbase_clear(ordered_lb); @@ -2398,9 +2286,6 @@ void BKE_id_ordered_list(ListBase *ordered_lb, const ListBase *lb) } } -/** - * Reorder ID in the list, before or after the "relative" ID. - */ void BKE_id_reorder(const ListBase *lb, ID *id, ID *relative, bool after) { int *id_order = id_order_get(id); diff --git a/source/blender/blenkernel/intern/lib_id_delete.c b/source/blender/blenkernel/intern/lib_id_delete.c index 502a1197616..f4dd67cac28 100644 --- a/source/blender/blenkernel/intern/lib_id_delete.c +++ b/source/blender/blenkernel/intern/lib_id_delete.c @@ -90,23 +90,6 @@ void BKE_libblock_free_datablock(ID *id, const int UNUSED(flag)) BLI_assert_msg(0, "IDType Missing IDTypeInfo"); } -/** - * Complete ID freeing, extended version for corner cases. - * Can override default (and safe!) freeing process, to gain some speed up. - * - * At that point, given id is assumed to not be used by any other data-block already - * (might not be actually true, in case e.g. several inter-related IDs get freed together...). - * However, they might still be using (referencing) other IDs, this code takes care of it if - * #LIB_TAG_NO_USER_REFCOUNT is not defined. - * - * \param bmain: #Main database containing the freed #ID, - * can be NULL in case it's a temp ID outside of any #Main. - * \param idv: Pointer to ID to be freed. - * \param flag: Set of \a LIB_ID_FREE_... flags controlling/overriding usual freeing process, - * 0 to get default safe behavior. - * \param use_flag_from_idtag: Still use freeing info flags from given #ID datablock, - * even if some overriding ones are passed in \a flag parameter. - */ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_idtag) { ID *id = idv; @@ -171,7 +154,10 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i } if (remap_editor_id_reference_cb) { - remap_editor_id_reference_cb(id, NULL); + struct IDRemapper *remapper = BKE_id_remapper_create(); + BKE_id_remapper_add(remapper, id, NULL); + remap_editor_id_reference_cb(remapper); + BKE_id_remapper_free(remapper); } } @@ -191,24 +177,11 @@ void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_i } } -/** - * Complete ID freeing, should be usable in most cases (even for out-of-Main IDs). - * - * See #BKE_id_free_ex description for full details. - * - * \param bmain: Main database containing the freed ID, - * can be NULL in case it's a temp ID outside of any Main. - * \param idv: Pointer to ID to be freed. - */ void BKE_id_free(Main *bmain, void *idv) { BKE_id_free_ex(bmain, idv, 0, true); } -/** - * Not really a freeing function by itself, - * it decrements usercount of given id, and only frees it if it reaches 0. - */ void BKE_id_free_us(Main *bmain, void *idv) /* test users */ { ID *id = idv; @@ -322,32 +295,40 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion) * Note that we go forward here, since we want to check dependencies before users * (e.g. meshes before objects). * Avoids to have to loop twice. */ + struct IDRemapper *remapper = BKE_id_remapper_create(); for (i = 0; i < base_count; i++) { ListBase *lb = lbarray[i]; ID *id, *id_next; + BKE_id_remapper_clear(remapper); 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! */ if ((id->tag & tag) || (id->lib != NULL && (id->lib->id.tag & tag))) { id->tag |= tag; - - /* Will tag 'never NULL' users of this ID too. - * Note that we cannot use BKE_libblock_unlink() here, since it would ignore indirect - * (and proxy!) links, this can lead to nasty crashing here in second, - * actual deleting loop. - * Also, this will also flag users of deleted data that cannot be unlinked - * (object using deleted obdata, etc.), so that they also get deleted. */ - BKE_libblock_remap_locked(bmain, - id, - NULL, - (ID_REMAP_FLAG_NEVER_NULL_USAGE | - ID_REMAP_FORCE_NEVER_NULL_USAGE | - ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS)); + BKE_id_remapper_add(remapper, id, NULL); } } + + if (BKE_id_remapper_is_empty(remapper)) { + continue; + } + + /* Will tag 'never NULL' users of this ID too. + * Note that we cannot use BKE_libblock_unlink() here, since it would ignore indirect + * (and proxy!) links, this can lead to nasty crashing here in second, + * actual deleting loop. + * Also, this will also flag users of deleted data that cannot be unlinked + * (object using deleted obdata, etc.), so that they also get deleted. */ + BKE_libblock_remap_multiple_locked(bmain, + remapper, + (ID_REMAP_FLAG_NEVER_NULL_USAGE | + ID_REMAP_FORCE_NEVER_NULL_USAGE | + ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS)); } + BKE_id_remapper_free(remapper); } + BKE_main_unlock(bmain); /* In usual reversed order, such that all usage of a given ID, even 'never NULL' ones, @@ -378,27 +359,17 @@ static size_t id_delete(Main *bmain, const bool do_tagged_deletion) return num_datablocks_deleted; } -/** - * Properly delete a single ID from given \a bmain database. - */ void BKE_id_delete(Main *bmain, void *idv) { + BLI_assert_msg((((ID *)idv)->tag & LIB_TAG_NO_MAIN) == 0, + "Cannot be used with IDs outside of Main"); + BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); ((ID *)idv)->tag |= LIB_TAG_DOIT; id_delete(bmain, false); } -/** - * Properly delete all IDs tagged with \a LIB_TAG_DOIT, in given \a bmain database. - * - * This is more efficient than calling #BKE_id_delete repetitively on a large set of IDs - * (several times faster when deleting most of the IDs at once)... - * - * \warning Considered experimental for now, seems to be working OK but this is - * risky code in a complicated area. - * \return Number of deleted datablocks. - */ size_t BKE_id_multi_tagged_delete(Main *bmain) { return id_delete(bmain, true); @@ -408,12 +379,6 @@ size_t BKE_id_multi_tagged_delete(Main *bmain) /** \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 diff --git a/source/blender/blenkernel/intern/lib_id_eval.c b/source/blender/blenkernel/intern/lib_id_eval.c index 140fe403ac3..a29d9270d72 100644 --- a/source/blender/blenkernel/intern/lib_id_eval.c +++ b/source/blender/blenkernel/intern/lib_id_eval.c @@ -29,11 +29,6 @@ #include "BKE_lib_id.h" #include "BKE_mesh.h" -/** - * Copy relatives parameters, from `id` to `id_cow`. - * Use handle the #ID_RECALC_PARAMETERS tag. - * \note Keep in sync with #ID_TYPE_SUPPORTS_PARAMS_WITHOUT_COW. - */ void BKE_id_eval_properties_copy(ID *id_cow, ID *id) { const ID_Type id_type = GS(id->name); diff --git a/source/blender/blenkernel/intern/lib_id_remapper.cc b/source/blender/blenkernel/intern/lib_id_remapper.cc new file mode 100644 index 00000000000..c1734c9826a --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_remapper.cc @@ -0,0 +1,175 @@ +/* + * 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) 2022 by Blender Foundation. + */ + +#include "DNA_ID.h" + +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_lib_remap.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_map.hh" + +using IDTypeFilter = uint64_t; + +namespace blender::bke::id::remapper { +struct IDRemapper { + private: + Map<ID *, ID *> mappings; + IDTypeFilter source_types = 0; + + public: + void clear() + { + mappings.clear(); + source_types = 0; + } + + bool is_empty() const + { + return mappings.is_empty(); + } + + void add(ID *old_id, ID *new_id) + { + BLI_assert(old_id != nullptr); + BLI_assert(new_id == nullptr || (GS(old_id->name) == GS(new_id->name))); + mappings.add(old_id, new_id); + source_types |= BKE_idtype_idcode_to_idfilter(GS(old_id->name)); + } + + bool contains_mappings_for_any(IDTypeFilter filter) const + { + return (source_types & filter) != 0; + } + + IDRemapperApplyResult apply(ID **r_id_ptr, IDRemapperApplyOptions options) const + { + BLI_assert(r_id_ptr != nullptr); + if (*r_id_ptr == nullptr) { + return ID_REMAP_RESULT_SOURCE_NOT_MAPPABLE; + } + + if (!mappings.contains(*r_id_ptr)) { + return ID_REMAP_RESULT_SOURCE_UNAVAILABLE; + } + + if (options & ID_REMAP_APPLY_UPDATE_REFCOUNT) { + id_us_min(*r_id_ptr); + } + + *r_id_ptr = mappings.lookup(*r_id_ptr); + if (*r_id_ptr == nullptr) { + return ID_REMAP_RESULT_SOURCE_UNASSIGNED; + } + + if (options & ID_REMAP_APPLY_UPDATE_REFCOUNT) { + id_us_plus(*r_id_ptr); + } + + if (options & ID_REMAP_APPLY_ENSURE_REAL) { + id_us_ensure_real(*r_id_ptr); + } + return ID_REMAP_RESULT_SOURCE_REMAPPED; + } + + void iter(IDRemapperIterFunction func, void *user_data) const + { + for (auto item : mappings.items()) { + func(item.key, item.value, user_data); + } + } +}; + +} // namespace blender::bke::id::remapper + +/** \brief wrap CPP IDRemapper to a C handle. */ +static IDRemapper *wrap(blender::bke::id::remapper::IDRemapper *remapper) +{ + return static_cast<IDRemapper *>(static_cast<void *>(remapper)); +} + +/** \brief wrap C handle to a CPP IDRemapper. */ +static blender::bke::id::remapper::IDRemapper *unwrap(IDRemapper *remapper) +{ + return static_cast<blender::bke::id::remapper::IDRemapper *>(static_cast<void *>(remapper)); +} + +/** \brief wrap C handle to a CPP IDRemapper. */ +static const blender::bke::id::remapper::IDRemapper *unwrap(const IDRemapper *remapper) +{ + return static_cast<const blender::bke::id::remapper::IDRemapper *>( + static_cast<const void *>(remapper)); +} + +extern "C" { + +IDRemapper *BKE_id_remapper_create(void) +{ + blender::bke::id::remapper::IDRemapper *remapper = + MEM_new<blender::bke::id::remapper::IDRemapper>(__func__); + return wrap(remapper); +} + +void BKE_id_remapper_free(IDRemapper *id_remapper) +{ + blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + MEM_delete<blender::bke::id::remapper::IDRemapper>(remapper); +} + +void BKE_id_remapper_clear(struct IDRemapper *id_remapper) +{ + blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + remapper->clear(); +} + +bool BKE_id_remapper_is_empty(const struct IDRemapper *id_remapper) +{ + const blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + return remapper->is_empty(); +} + +void BKE_id_remapper_add(IDRemapper *id_remapper, ID *old_id, ID *new_id) +{ + blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + remapper->add(old_id, new_id); +} + +bool BKE_id_remapper_has_mapping_for(const struct IDRemapper *id_remapper, uint64_t type_filter) +{ + const blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + return remapper->contains_mappings_for_any(type_filter); +} + +IDRemapperApplyResult BKE_id_remapper_apply(const IDRemapper *id_remapper, + ID **r_id_ptr, + const IDRemapperApplyOptions options) +{ + const blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + return remapper->apply(r_id_ptr, options); +} + +void BKE_id_remapper_iter(const struct IDRemapper *id_remapper, + IDRemapperIterFunction func, + void *user_data) +{ + const blender::bke::id::remapper::IDRemapper *remapper = unwrap(id_remapper); + remapper->iter(func, user_data); +} +} diff --git a/source/blender/blenkernel/intern/lib_id_remapper_test.cc b/source/blender/blenkernel/intern/lib_id_remapper_test.cc new file mode 100644 index 00000000000..594f64dac73 --- /dev/null +++ b/source/blender/blenkernel/intern/lib_id_remapper_test.cc @@ -0,0 +1,83 @@ +/* + * 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) 2022 by Blender Foundation. + */ + +#include "testing/testing.h" + +#include "BKE_lib_remap.h" + +#include "BLI_string.h" + +#include "DNA_ID.h" + +namespace blender::bke::id::remapper::tests { + +TEST(lib_id_remapper, unavailable) +{ + ID id1; + ID *idp = &id1; + + IDRemapper *remapper = BKE_id_remapper_create(); + IDRemapperApplyResult result = BKE_id_remapper_apply(remapper, &idp, ID_REMAP_APPLY_DEFAULT); + EXPECT_EQ(result, ID_REMAP_RESULT_SOURCE_UNAVAILABLE); + + BKE_id_remapper_free(remapper); +} + +TEST(lib_id_remapper, not_mappable) +{ + ID *idp = nullptr; + + IDRemapper *remapper = BKE_id_remapper_create(); + IDRemapperApplyResult result = BKE_id_remapper_apply(remapper, &idp, ID_REMAP_APPLY_DEFAULT); + EXPECT_EQ(result, ID_REMAP_RESULT_SOURCE_NOT_MAPPABLE); + + BKE_id_remapper_free(remapper); +} + +TEST(lib_id_remapper, mapped) +{ + ID id1; + ID id2; + ID *idp = &id1; + BLI_strncpy(id1.name, "OB1", sizeof(id1.name)); + BLI_strncpy(id2.name, "OB2", sizeof(id2.name)); + + IDRemapper *remapper = BKE_id_remapper_create(); + BKE_id_remapper_add(remapper, &id1, &id2); + IDRemapperApplyResult result = BKE_id_remapper_apply(remapper, &idp, ID_REMAP_APPLY_DEFAULT); + EXPECT_EQ(result, ID_REMAP_RESULT_SOURCE_REMAPPED); + EXPECT_EQ(idp, &id2); + + BKE_id_remapper_free(remapper); +} + +TEST(lib_id_remapper, unassigned) +{ + ID id1; + ID *idp = &id1; + + IDRemapper *remapper = BKE_id_remapper_create(); + BKE_id_remapper_add(remapper, &id1, nullptr); + IDRemapperApplyResult result = BKE_id_remapper_apply(remapper, &idp, ID_REMAP_APPLY_DEFAULT); + EXPECT_EQ(result, ID_REMAP_RESULT_SOURCE_UNASSIGNED); + EXPECT_EQ(idp, nullptr); + + BKE_id_remapper_free(remapper); +} + +} // namespace blender::bke::id::remapper::tests diff --git a/source/blender/blenkernel/intern/lib_override.c b/source/blender/blenkernel/intern/lib_override.c index 68675e5fc91..d1375b1e5b5 100644 --- a/source/blender/blenkernel/intern/lib_override.c +++ b/source/blender/blenkernel/intern/lib_override.c @@ -57,6 +57,7 @@ #include "BLI_ghash.h" #include "BLI_linklist.h" #include "BLI_listbase.h" +#include "BLI_memarena.h" #include "BLI_string.h" #include "BLI_task.h" #include "BLI_utildefines.h" @@ -99,7 +100,6 @@ BLI_INLINE IDOverrideLibrary *lib_override_get(Main *bmain, ID *id) return id->override_library; } -/** Initialize empty overriding of \a reference_id by \a local_id. */ 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. @@ -134,7 +134,6 @@ IDOverrideLibrary *BKE_lib_override_library_init(ID *local_id, ID *reference_id) return local_id->override_library; } -/** Shalow or deep copy of a whole override from \a src_id to \a dst_id. */ void BKE_lib_override_library_copy(ID *dst_id, const ID *src_id, const bool do_full_copy) { BLI_assert(ID_IS_OVERRIDE_LIBRARY(src_id) || ID_IS_OVERRIDE_LIBRARY_TEMPLATE(src_id)); @@ -176,7 +175,6 @@ void BKE_lib_override_library_copy(ID *dst_id, const ID *src_id, const bool do_f dst_id->tag &= ~LIB_TAG_OVERRIDE_LIBRARY_REFOK; } -/** Clear any overriding data from given \a override. */ void BKE_lib_override_library_clear(IDOverrideLibrary *override, const bool do_id_user) { BLI_assert(override != NULL); @@ -196,7 +194,6 @@ void BKE_lib_override_library_clear(IDOverrideLibrary *override, const bool do_i } } -/** Free given \a override. */ void BKE_lib_override_library_free(struct IDOverrideLibrary **override, const bool do_id_user) { BLI_assert(*override != NULL); @@ -245,14 +242,11 @@ static ID *lib_override_library_create_from(Main *bmain, return local_id; } -/** - * Check if given ID has some override rules that actually indicate the user edited it. - * - * TODO: This could be simplified by storing a flag in #IDOverrideLibrary during the diffing - * process? - */ +/* TODO: This could be simplified by storing a flag in #IDOverrideLibrary + * during the diffing process? */ bool BKE_lib_override_library_is_user_edited(struct ID *id) { + if (!ID_IS_OVERRIDE_LIBRARY(id)) { return false; } @@ -280,7 +274,6 @@ bool BKE_lib_override_library_is_user_edited(struct ID *id) return false; } -/** Create an overridden local copy of linked reference. */ ID *BKE_lib_override_library_create_from_id(Main *bmain, ID *reference_id, const bool do_tagged_remap) @@ -289,6 +282,10 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, BLI_assert(ID_IS_LINKED(reference_id)); ID *local_id = lib_override_library_create_from(bmain, reference_id, 0); + /* We cannot allow automatic hierarchy resync on this ID, it is highly likely to generate a giant + * mess in case there are a lot of hidden, non-instantiated, non-properly organized dependencies. + * Ref T94650. */ + local_id->override_library->flag |= IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY; if (do_tagged_remap) { Key *reference_key, *local_key = NULL; @@ -322,25 +319,6 @@ ID *BKE_lib_override_library_create_from_id(Main *bmain, return local_id; } -/** - * Create overridden local copies of all tagged data-blocks in given Main. - * - * \note Set `id->newid` of overridden libs with newly created overrides, - * caller is responsible to clean those pointers before/after usage as needed. - * - * \note By default, it will only remap newly created local overriding data-blocks between - * themselves, to avoid 'enforcing' those overrides into all other usages of the linked data in - * 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 - * (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. - * - * \return \a true on success, \a false otherwise. - */ bool BKE_lib_override_library_create_from_tag(Main *bmain, const Library *reference_library, const bool do_no_main) @@ -398,7 +376,6 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, * existing linked IDs usages. */ if (success) { for (todo_id_iter = todo_ids.first; todo_id_iter != NULL; todo_id_iter = todo_id_iter->next) { - ID *other_id; reference_id = todo_id_iter->data; ID *local_id = reference_id->newid; @@ -416,6 +393,7 @@ bool BKE_lib_override_library_create_from_tag(Main *bmain, * remapped to use newly created overriding IDs, if needed. */ ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { + ID *other_id; /* In case we created new overrides as 'no main', they are not accessible directly in this * loop, but we can get to them through their reference's `newid` pointer. */ if (do_no_main && id->lib == reference_id->lib && id->newid != NULL) { @@ -479,8 +457,60 @@ typedef struct LibOverrideGroupTagData { bool is_override; /* Whether we are creating new override, or resyncing existing one. */ bool is_resync; + + /* Mapping linked objects to all their instantiating collections (as a linked list). + * Avoids calling #BKE_collection_object_find over and over, this function is very expansive. */ + GHash *linked_object_to_instantiating_collections; + MemArena *mem_arena; } LibOverrideGroupTagData; +static void lib_override_group_tag_data_object_to_collection_init_collection_process( + LibOverrideGroupTagData *data, Collection *collection) +{ + LISTBASE_FOREACH (CollectionObject *, collection_object, &collection->gobject) { + Object *ob = collection_object->ob; + if (!ID_IS_LINKED(ob)) { + continue; + } + + LinkNodePair **collections_linkedlist_p; + if (!BLI_ghash_ensure_p(data->linked_object_to_instantiating_collections, + ob, + (void ***)&collections_linkedlist_p)) { + *collections_linkedlist_p = BLI_memarena_calloc(data->mem_arena, + sizeof(**collections_linkedlist_p)); + } + BLI_linklist_append_arena(*collections_linkedlist_p, collection, data->mem_arena); + } +} + +/* Initialize complex data, `data` is expected to be already initialized with basic pointers and + * other simple data. + * + * NOTE: Currently creates a mapping from linked object to all of their instantiating collections + * (as returned by #BKE_collection_object_find). */ +static void lib_override_group_tag_data_object_to_collection_init(LibOverrideGroupTagData *data) +{ + data->mem_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + + data->linked_object_to_instantiating_collections = BLI_ghash_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); + if (data->scene != NULL) { + lib_override_group_tag_data_object_to_collection_init_collection_process( + data, data->scene->master_collection); + } + LISTBASE_FOREACH (Collection *, collection, &data->bmain->collections) { + lib_override_group_tag_data_object_to_collection_init_collection_process(data, collection); + } +} + +static void lib_override_group_tag_data_clear(LibOverrideGroupTagData *data) +{ + BLI_ghash_free(data->linked_object_to_instantiating_collections, NULL, NULL); + BLI_memarena_free(data->mem_arena); + memset(data, 0, sizeof(*data)); +} + /* Tag all IDs in dependency relationships within an override hierarchy/group. * * Requires existing `Main.relations`. @@ -586,6 +616,42 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat } } +static void lib_override_linked_group_tag_clear_boneshapes_objects(LibOverrideGroupTagData *data) +{ + Main *bmain = data->bmain; + + /* Remove (untag) bone shape objects, they shall never need to be to directly/explicitly + * overridden. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ob->type == OB_ARMATURE && ob->pose != NULL && (ob->id.tag & data->tag)) { + for (bPoseChannel *pchan = ob->pose->chanbase.first; pchan != NULL; pchan = pchan->next) { + if (pchan->custom != NULL) { + pchan->custom->id.tag &= ~data->tag; + } + } + } + } + + /* Remove (untag) collections if they do not own any tagged object (either themselves, or in + * their children collections). */ + LISTBASE_FOREACH (Collection *, collection, &bmain->collections) { + if ((collection->id.tag & data->tag) == 0) { + continue; + } + bool keep_tagged = false; + const ListBase object_bases = BKE_collection_object_cache_get(collection); + LISTBASE_FOREACH (Base *, base, &object_bases) { + if ((base->object->id.tag & data->tag) != 0) { + keep_tagged = true; + break; + } + } + if (!keep_tagged) { + collection->id.tag &= ~data->tag; + } + } +} + /* This will tag at least all 'boundary' linked IDs for a potential override group. * * Requires existing `Main.relations`. @@ -599,7 +665,6 @@ static void lib_override_linked_group_tag_recursive(LibOverrideGroupTagData *dat static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) { Main *bmain = data->bmain; - Scene *scene = data->scene; ID *id_root = data->id_root; const bool is_resync = data->is_resync; BLI_assert(!data->is_override); @@ -611,36 +676,36 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) id_root->tag |= data->tag; } - if (ELEM(GS(id_root->name), ID_OB, ID_GR)) { - /* Tag all collections and objects. */ - lib_override_linked_group_tag_recursive(data); + /* Only objects and groups are currently considered as 'keys' in override hierarchies. */ + if (!ELEM(GS(id_root->name), ID_OB, ID_GR)) { + return; + } - /* Then, we remove (untag) bone shape objects, you shall never want to directly/explicitly - * override those. */ - LISTBASE_FOREACH (Object *, ob, &bmain->objects) { - if (ob->type == OB_ARMATURE && ob->pose != NULL && (ob->id.tag & data->tag)) { - for (bPoseChannel *pchan = ob->pose->chanbase.first; pchan != NULL; pchan = pchan->next) { - if (pchan->custom != NULL) { - pchan->custom->id.tag &= ~(data->tag | data->missing_tag); - } - } - } - } + /* Tag all collections and objects recursively. */ + lib_override_linked_group_tag_recursive(data); - /* For each object tagged for override, ensure we get at least one local or liboverride - * collection to host it. Avoids getting a bunch of random object in the scene's master - * collection when all objects' dependencies are not properly 'packed' into a single root - * collection. */ - LISTBASE_FOREACH (Object *, ob, &bmain->objects) { - if (ID_IS_LINKED(ob) && (ob->id.tag & data->tag) != 0) { - Collection *instantiating_collection = NULL; - Collection *instantiating_collection_override_candidate = NULL; - /* Loop over all collections instantiating the object, if we already have a 'locale' one we - * have nothing to do, otherwise try to find a 'linked' one that we can override too. */ - while ((instantiating_collection = BKE_collection_object_find( - bmain, scene, instantiating_collection, ob)) != NULL) { - /* In (recursive) resync case, if a collection of a 'parent' lib instantiates the linked - * object, it is also fine. */ + /* Do not override objects used as bone shapes, nor their collections if possible. */ + lib_override_linked_group_tag_clear_boneshapes_objects(data); + + /* For each object tagged for override, ensure we get at least one local or liboverride + * collection to host it. Avoids getting a bunch of random object in the scene's master + * collection when all objects' dependencies are not properly 'packed' into a single root + * collection. */ + LISTBASE_FOREACH (Object *, ob, &bmain->objects) { + if (ID_IS_LINKED(ob) && (ob->id.tag & data->tag) != 0) { + Collection *instantiating_collection = NULL; + Collection *instantiating_collection_override_candidate = NULL; + /* Loop over all collections instantiating the object, if we already have a 'locale' one we + * have nothing to do, otherwise try to find a 'linked' one that we can override too. */ + LinkNodePair *instantiating_collection_linklist = BLI_ghash_lookup( + data->linked_object_to_instantiating_collections, ob); + if (instantiating_collection_linklist != NULL) { + for (LinkNode *instantiating_collection_linknode = instantiating_collection_linklist->list; + instantiating_collection_linknode != NULL; + instantiating_collection_linknode = instantiating_collection_linknode->next) { + instantiating_collection = instantiating_collection_linknode->link; + /* In (recursive) resync case, if a collection of a 'parent' lib instantiates the + * linked object, it is also fine. */ if (!ID_IS_LINKED(instantiating_collection) || (is_resync && ID_IS_LINKED(id_root) && instantiating_collection->id.lib->temp_index < id_root->lib->temp_index)) { @@ -650,16 +715,17 @@ static void lib_override_linked_group_tag(LibOverrideGroupTagData *data) (!is_resync || instantiating_collection->id.lib == id_root->lib)) { instantiating_collection_override_candidate = instantiating_collection; } + instantiating_collection = NULL; } + } - if (instantiating_collection == NULL && - instantiating_collection_override_candidate != NULL) { - if (instantiating_collection_override_candidate->id.tag & LIB_TAG_MISSING) { - instantiating_collection_override_candidate->id.tag |= data->missing_tag; - } - else { - instantiating_collection_override_candidate->id.tag |= data->tag; - } + if (instantiating_collection == NULL && + instantiating_collection_override_candidate != NULL) { + if (instantiating_collection_override_candidate->id.tag & LIB_TAG_MISSING) { + instantiating_collection_override_candidate->id.tag |= data->missing_tag; + } + else { + instantiating_collection_override_candidate->id.tag |= data->tag; } } } @@ -672,6 +738,12 @@ static void lib_override_overrides_group_tag_recursive(LibOverrideGroupTagData * ID *id_owner = data->id_root; BLI_assert(ID_IS_OVERRIDE_LIBRARY(id_owner)); BLI_assert(data->is_override); + + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_owner) && + (id_owner->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY) != 0) { + return; + } + const uint tag = data->tag; const uint missing_tag = data->missing_tag; @@ -751,12 +823,14 @@ static bool lib_override_library_create_do(Main *bmain, Scene *scene, ID *id_roo .missing_tag = LIB_TAG_MISSING, .is_override = false, .is_resync = false}; + lib_override_group_tag_data_object_to_collection_init(&data); lib_override_linked_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); lib_override_hierarchy_dependencies_recursive_tag(&data); BKE_main_relations_free(bmain); + lib_override_group_tag_data_clear(&data); return BKE_lib_override_library_create_from_tag(bmain, id_root->lib, false); } @@ -890,24 +964,6 @@ static void lib_override_library_create_post_process(Main *bmain, BLI_gset_free(all_objects_in_scene, NULL); } -/** - * Advanced 'smart' function to create fully functional overrides. - * - * \note Currently it only does special things if given \a id_root is an object or collection, more - * specific behaviors may be added in the future for other ID types. - * - * \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. - * \return true if override was successfully created. - */ bool BKE_lib_override_library_create(Main *bmain, Scene *scene, ViewLayer *view_layer, @@ -942,9 +998,6 @@ bool BKE_lib_override_library_create(Main *bmain, return success; } -/** - * Create a library override template. - */ bool BKE_lib_override_library_template_create(struct ID *id) { if (ID_IS_LINKED(id)) { @@ -958,16 +1011,6 @@ bool BKE_lib_override_library_template_create(struct ID *id) return true; } -/** - * Convert a given proxy object into a library override. - * - * \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, Scene *scene, ViewLayer *view_layer, @@ -1028,7 +1071,7 @@ static void lib_override_library_proxy_convert_do(Main *bmain, if (success) { CLOG_INFO(&LOG, 4, - "Proxy object '%s' successfuly converted to library overrides", + "Proxy object '%s' successfully converted to library overrides", ob_proxy->id.name); /* Remove the instance empty from this scene, the items now have an overridden collection * instead. */ @@ -1039,14 +1082,6 @@ static void lib_override_library_proxy_convert_do(Main *bmain, } } -/** - * 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) { @@ -1086,23 +1121,53 @@ void BKE_lib_override_library_main_proxy_convert(Main *bmain, BlendFileReadRepor } } -/** - * 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, - Scene *scene, - ViewLayer *view_layer, - ID *id_root, - Collection *override_resync_residual_storage, - const bool do_hierarchy_enforce, - const bool do_post_process, - BlendFileReadReport *reports) +static void lib_override_library_remap(Main *bmain, + const ID *id_root_reference, + GHash *linkedref_to_old_override) +{ + ID *id; + struct IDRemapper *remapper = BKE_id_remapper_create(); + FOREACH_MAIN_ID_BEGIN (bmain, id) { + + if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { + ID *id_override_new = id->newid; + ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); + if (id_override_old == NULL) { + continue; + } + + BKE_id_remapper_add(remapper, id_override_old, id_override_new); + /* Remap no-main override IDs we just created too. */ + GHashIterator linkedref_to_old_override_iter; + GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) { + ID *id_override_old_iter = BLI_ghashIterator_getValue(&linkedref_to_old_override_iter); + if ((id_override_old_iter->tag & LIB_TAG_NO_MAIN) == 0) { + continue; + } + + BKE_libblock_relink_ex(bmain, + id_override_old_iter, + id_override_old, + id_override_new, + ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); + } + } + } + FOREACH_MAIN_ID_END; + + /* Remap all IDs to use the new override. */ + BKE_libblock_remap_multiple(bmain, remapper, 0); + BKE_id_remapper_free(remapper); +} + +static bool lib_override_library_resync(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ID *id_root, + Collection *override_resync_residual_storage, + const bool do_hierarchy_enforce, + const bool do_post_process, + BlendFileReadReport *reports) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); @@ -1125,6 +1190,7 @@ bool BKE_lib_override_library_resync(Main *bmain, .missing_tag = LIB_TAG_MISSING, .is_override = true, .is_resync = true}; + lib_override_group_tag_data_object_to_collection_init(&data); lib_override_overrides_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); @@ -1215,6 +1281,7 @@ bool BKE_lib_override_library_resync(Main *bmain, lib_override_hierarchy_dependencies_recursive_tag(&data); BKE_main_relations_free(bmain); + lib_override_group_tag_data_clear(&data); /* Make new override from linked data. */ /* Note that this call also remaps all pointers of tagged IDs from old override IDs to new @@ -1284,32 +1351,9 @@ bool BKE_lib_override_library_resync(Main *bmain, } FOREACH_MAIN_LISTBASE_END; - /* We need to remap old to new override usages in a separate loop, after all new overrides have + /* We remap old to new override usages in a separate loop, after all new overrides have * been added to Main. */ - FOREACH_MAIN_ID_BEGIN (bmain, id) { - if (id->tag & LIB_TAG_DOIT && id->newid != NULL && id->lib == id_root_reference->lib) { - ID *id_override_new = id->newid; - ID *id_override_old = BLI_ghash_lookup(linkedref_to_old_override, id); - - if (id_override_old != NULL) { - /* Remap all IDs to use the new override. */ - BKE_libblock_remap(bmain, id_override_old, id_override_new, 0); - /* Remap no-main override IDs we just created too. */ - GHashIterator linkedref_to_old_override_iter; - GHASH_ITER (linkedref_to_old_override_iter, linkedref_to_old_override) { - ID *id_override_old_iter = BLI_ghashIterator_getValue(&linkedref_to_old_override_iter); - if (id_override_old_iter->tag & LIB_TAG_NO_MAIN) { - BKE_libblock_relink_ex(bmain, - id_override_old_iter, - id_override_old, - id_override_new, - ID_REMAP_FORCE_USER_REFCOUNT | ID_REMAP_FORCE_NEVER_NULL_USAGE); - } - } - } - } - } - FOREACH_MAIN_ID_END; + lib_override_library_remap(bmain, id_root_reference, linkedref_to_old_override); BKE_main_collection_sync(bmain); @@ -1471,6 +1515,26 @@ bool BKE_lib_override_library_resync(Main *bmain, return success; } +bool BKE_lib_override_library_resync(Main *bmain, + Scene *scene, + ViewLayer *view_layer, + ID *id_root, + Collection *override_resync_residual_storage, + const bool do_hierarchy_enforce, + BlendFileReadReport *reports) +{ + const bool success = lib_override_library_resync(bmain, + scene, + view_layer, + id_root, + override_resync_residual_storage, + do_hierarchy_enforce, + true, + reports); + + return success; +} + /* Also tag ancestors overrides for resync. * * WARNING: Expects `bmain` to have valid relation data. @@ -1489,11 +1553,13 @@ static void lib_override_resync_tagging_finalize_recurse(Main *bmain, CLOG_ERROR( &LOG, "While processing indirect level %d, ID %s from lib %s of indirect level %d detected " - "as needing resync.", + "as needing resync, skipping.", library_indirect_level, id->name, id->lib->filepath, id->lib->temp_index); + id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; + return; } MainIDRelationsEntry *entry = BLI_ghash_lookup(bmain->relations->relations_from_pointers, id); @@ -1607,6 +1673,14 @@ static void lib_override_library_main_resync_on_library_indirect_level( /* Detect all linked data that would need to be overridden if we had to create an override from * those used by current existing overrides. */ + LibOverrideGroupTagData data = {.bmain = bmain, + .scene = scene, + .id_root = NULL, + .tag = LIB_TAG_DOIT, + .missing_tag = LIB_TAG_MISSING, + .is_override = false, + .is_resync = true}; + lib_override_group_tag_data_object_to_collection_init(&data); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { @@ -1617,19 +1691,19 @@ static void lib_override_library_main_resync_on_library_indirect_level( continue; } - LibOverrideGroupTagData data = {.bmain = bmain, - .scene = scene, - .id_root = id->override_library->reference, - .tag = LIB_TAG_DOIT, - .missing_tag = LIB_TAG_MISSING, - .is_override = false, - .is_resync = true}; + if (id->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY) { + /* This ID is not part of an override hierarchy. */ + continue; + } + + data.id_root = id->override_library->reference; lib_override_linked_group_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); lib_override_hierarchy_dependencies_recursive_tag(&data); BKE_main_relations_tag_set(bmain, MAINIDRELATIONS_ENTRY_TAGS_PROCESSED, false); } FOREACH_MAIN_ID_END; + lib_override_group_tag_data_clear(&data); /* Now check existing overrides, those needing resync will be the one either already tagged as * such, or the one using linked data that is now tagged as needing override. */ @@ -1639,6 +1713,12 @@ static void lib_override_library_main_resync_on_library_indirect_level( continue; } + if (id->override_library->flag & IDOVERRIDE_LIBRARY_FLAG_NO_HIERARCHY) { + /* This ID is not part of an override hierarchy. */ + BLI_assert((id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) == 0); + continue; + } + if (id->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC) { CLOG_INFO(&LOG, 4, "ID %s (%p) was already tagged as needing resync", id->name, id->lib); lib_override_resync_tagging_finalize_recurse(bmain, id, library_indirect_level); @@ -1691,6 +1771,10 @@ static void lib_override_library_main_resync_on_library_indirect_level( continue; } + if (ID_IS_LINKED(id)) { + id->lib->tag |= LIBRARY_TAG_RESYNC_REQUIRED; + } + /* We cannot resync a scene that is currently active. */ if (id == &scene->id) { id->tag &= ~LIB_TAG_LIB_OVERRIDE_NEED_RESYNC; @@ -1717,7 +1801,7 @@ static void lib_override_library_main_resync_on_library_indirect_level( do_continue = true; CLOG_INFO(&LOG, 2, "Resyncing %s (%p)...", id->name, library); - const bool success = BKE_lib_override_library_resync( + const bool success = lib_override_library_resync( bmain, scene, view_layer, id, override_resync_residual_storage, false, false, reports); CLOG_INFO(&LOG, 2, "\tSuccess: %d", success); if (success) { @@ -1801,23 +1885,6 @@ static int lib_override_libraries_index_define(Main *bmain) return library_indirect_level_max; } -/** - * Detect and handle required resync of overrides data, when relations between reference linked IDs - * have changed. - * - * This is a fairly complex and costly operation, typically it should be called after - * #BKE_lib_override_library_main_update, which would already detect and tag a lot of cases. - * - * This function will first detect the remaining cases requiring a resync (namely, either when an - * existing linked ID that did not require to be overridden before now would be, or when new IDs - * are added to the hierarchy). - * - * 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, ViewLayer *view_layer, @@ -1864,16 +1931,18 @@ void BKE_lib_override_library_main_resync(Main *bmain, if (BKE_collection_is_empty(override_resync_residual_storage)) { BKE_collection_delete(bmain, override_resync_residual_storage, true); } + + LISTBASE_FOREACH (Library *, library, &bmain->libraries) { + if (library->tag & LIBRARY_TAG_RESYNC_REQUIRED) { + CLOG_INFO(&LOG, + 2, + "library '%s' contains some linked overrides that required recursive resync, " + "consider updating it", + library->filepath); + } + } } -/** - * Advanced 'smart' function to delete library overrides (including their existing override - * hierarchy) and remap their usages to their linked reference IDs. - * - * \note All IDs tagged with `LIB_TAG_DOIT` will be deleted. - * - * \param id_root: The root liboverride ID to delete. - */ void BKE_lib_override_library_delete(Main *bmain, ID *id_root) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(id_root)); @@ -1887,9 +1956,11 @@ void BKE_lib_override_library_delete(Main *bmain, ID *id_root) .missing_tag = LIB_TAG_MISSING, .is_override = true, .is_resync = false}; + lib_override_group_tag_data_object_to_collection_init(&data); lib_override_overrides_group_tag(&data); BKE_main_relations_free(bmain); + lib_override_group_tag_data_clear(&data); ID *id; FOREACH_MAIN_ID_BEGIN (bmain, id) { @@ -1911,13 +1982,18 @@ void BKE_lib_override_library_delete(Main *bmain, ID *id_root) BKE_main_id_tag_all(bmain, LIB_TAG_DOIT, false); } -/** Make given ID fully local. - * - * \note Only differs from lower-level `BKE_lib_override_library_free in infamous embedded ID - * cases. - */ 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); @@ -1962,9 +2038,6 @@ BLI_INLINE GHash *override_library_rna_path_mapping_ensure(IDOverrideLibrary *ov return override_runtime->rna_path_to_override_properties; } -/** - * Find override property from given RNA path, if it exists. - */ IDOverrideLibraryProperty *BKE_lib_override_library_property_find(IDOverrideLibrary *override, const char *rna_path) { @@ -1972,9 +2045,6 @@ IDOverrideLibraryProperty *BKE_lib_override_library_property_find(IDOverrideLibr return BLI_ghash_lookup(override_runtime, rna_path); } -/** - * Find override property from given RNA path, or create it if it does not exist. - */ IDOverrideLibraryProperty *BKE_lib_override_library_property_get(IDOverrideLibrary *override, const char *rna_path, bool *r_created) @@ -2000,13 +2070,6 @@ IDOverrideLibraryProperty *BKE_lib_override_library_property_get(IDOverrideLibra return op; } -/** - * Get the RNA-property matching the \a library_prop override property. Used for UI to query - * additional data about the overridden property (e.g. UI name). - * - * \param idpoin: Pointer to the override ID. - * \param library_prop: The library override property to find the matching RNA property for. - */ bool BKE_lib_override_rna_property_find(PointerRNA *idpoin, const IDOverrideLibraryProperty *library_prop, PointerRNA *r_override_poin, @@ -2043,9 +2106,6 @@ void lib_override_library_property_clear(IDOverrideLibraryProperty *op) BLI_freelistN(&op->operations); } -/** - * Remove and free given \a override_property from given ID \a override. - */ void BKE_lib_override_library_property_delete(IDOverrideLibrary *override, IDOverrideLibraryProperty *override_property) { @@ -2059,9 +2119,6 @@ void BKE_lib_override_library_property_delete(IDOverrideLibrary *override, BLI_freelinkN(&override->properties, override_property); } -/** - * Find override property operation from given sub-item(s), if it exists. - */ IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_find( IDOverrideLibraryProperty *override_property, const char *subitem_refname, @@ -2148,9 +2205,6 @@ IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_ return NULL; } -/** - * Find override property operation from given sub-item(s), or create it if it does not exist. - */ IDOverrideLibraryPropertyOperation *BKE_lib_override_library_property_operation_get( IDOverrideLibraryProperty *override_property, const short operation, @@ -2217,9 +2271,6 @@ void lib_override_library_property_operation_clear(IDOverrideLibraryPropertyOper } } -/** - * Remove and free given \a override_property_operation from given ID \a override_property. - */ void BKE_lib_override_library_property_operation_delete( IDOverrideLibraryProperty *override_property, IDOverrideLibraryPropertyOperation *override_property_operation) @@ -2228,9 +2279,6 @@ void BKE_lib_override_library_property_operation_delete( BLI_freelinkN(&override_property->operations, override_property_operation); } -/** - * Validate that required data for a given operation are available. - */ bool BKE_lib_override_library_property_operation_operands_validate( struct IDOverrideLibraryPropertyOperation *override_property_operation, struct PointerRNA *ptr_dst, @@ -2268,7 +2316,6 @@ bool BKE_lib_override_library_property_operation_operands_validate( return true; } -/** Check against potential \a bmain. */ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList *reports) { if (id->override_library == NULL) { @@ -2302,7 +2349,6 @@ void BKE_lib_override_library_validate(Main *UNUSED(bmain), ID *id, ReportList * } } -/** Check against potential \a bmain. */ void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports) { ID *id; @@ -2315,16 +2361,6 @@ void BKE_lib_override_library_main_validate(Main *bmain, ReportList *reports) FOREACH_MAIN_ID_END; } -/** - * Check that status of local data-block is still valid against current reference one. - * - * It means that all overridable, but not overridden, properties' local values must be equal to - * reference ones. Clears #LIB_TAG_OVERRIDE_OK if they do not. - * - * This is typically used to detect whether some property has been changed in local and a new - * #IDOverrideProperty (of #IDOverridePropertyOperation) has to be added. - * - * \return true if status is OK, false otherwise. */ bool BKE_lib_override_library_status_check_local(Main *bmain, ID *local) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local)); @@ -2374,16 +2410,6 @@ bool BKE_lib_override_library_status_check_local(Main *bmain, ID *local) return true; } -/** - * Check that status of reference data-block is still valid against current local one. - * - * It means that all non-overridden properties' local values must be equal to reference ones. - * Clears LIB_TAG_OVERRIDE_OK if they do not. - * - * This is typically used to detect whether some reference has changed and local - * needs to be updated against it. - * - * \return true if status is OK, false otherwise. */ bool BKE_lib_override_library_status_check_reference(Main *bmain, ID *local) { BLI_assert(ID_IS_OVERRIDE_LIBRARY_REAL(local)); @@ -2440,20 +2466,6 @@ bool BKE_lib_override_library_status_check_reference(Main *bmain, ID *local) return true; } -/** - * Compare local and reference data-blocks and create new override operations as needed, - * or reset to reference values if overriding is not allowed. - * - * \note Defining override operations is only mandatory before saving a `.blend` file on disk - * (not for undo!). - * Knowing that info at runtime is only useful for UI/UX feedback. - * - * \note This is by far the biggest operation (the more time-consuming) of the three so far, - * since it has to go over all properties in depth (all overridable ones at least). - * Generating differential values and applying overrides are much cheaper. - * - * \return true if any library operation was created. - */ bool BKE_lib_override_library_operations_create(Main *bmain, ID *local) { BLI_assert(local->override_library != NULL); @@ -2530,7 +2542,6 @@ static void lib_override_library_operations_create_cb(TaskPool *__restrict pool, } } -/** Check all overrides from given \a bmain and create/update overriding operations as needed. */ bool BKE_lib_override_library_main_operations_create(Main *bmain, const bool force_auto) { ID *id; @@ -2659,7 +2670,6 @@ static bool lib_override_library_id_reset_do(Main *bmain, ID *id_root) return was_op_deleted; } -/** Reset all overrides in given \a id_root, while preserving ID relations. */ void BKE_lib_override_library_id_reset(Main *bmain, ID *id_root) { if (!ID_IS_OVERRIDE_LIBRARY_REAL(id_root)) { @@ -2718,7 +2728,6 @@ static void lib_override_library_id_hierarchy_recursive_reset(Main *bmain, ID *i } } -/** Reset all overrides in given \a id_root and its dependencies, while preserving ID relations. */ void BKE_lib_override_library_id_hierarchy_reset(Main *bmain, ID *id_root) { BKE_main_relations_create(bmain, 0); @@ -2739,7 +2748,6 @@ void BKE_lib_override_library_id_hierarchy_reset(Main *bmain, ID *id_root) FOREACH_MAIN_ID_END; } -/** Set or clear given tag in all operations in that override property data. */ void BKE_lib_override_library_operations_tag(struct IDOverrideLibraryProperty *override_property, const short tag, const bool do_set) @@ -2763,7 +2771,6 @@ void BKE_lib_override_library_operations_tag(struct IDOverrideLibraryProperty *o } } -/** Set or clear given tag in all properties and operations in that override data. */ void BKE_lib_override_library_properties_tag(struct IDOverrideLibrary *override, const short tag, const bool do_set) @@ -2775,7 +2782,6 @@ void BKE_lib_override_library_properties_tag(struct IDOverrideLibrary *override, } } -/** Set or clear given tag in all properties and operations in that Main's ID override data. */ void BKE_lib_override_library_main_tag(struct Main *bmain, const short tag, const bool do_set) { ID *id; @@ -2788,7 +2794,6 @@ void BKE_lib_override_library_main_tag(struct Main *bmain, const short tag, cons FOREACH_MAIN_ID_END; } -/** Remove all tagged-as-unused properties and operations from that ID override data. */ void BKE_lib_override_library_id_unused_cleanup(struct ID *local) { if (ID_IS_OVERRIDE_LIBRARY_REAL(local)) { @@ -2808,7 +2813,6 @@ void BKE_lib_override_library_id_unused_cleanup(struct ID *local) } } -/** Remove all tagged-as-unused properties and operations from that Main's ID override data. */ void BKE_lib_override_library_main_unused_cleanup(struct Main *bmain) { ID *id; @@ -2829,7 +2833,6 @@ static void lib_override_id_swap(Main *bmain, ID *id_local, ID *id_temp) id_local->tag |= (id_temp->tag & LIB_TAG_LIB_OVERRIDE_NEED_RESYNC); } -/** Update given override from its reference (re-applying overridden properties). */ void BKE_lib_override_library_update(Main *bmain, ID *local) { if (!ID_IS_OVERRIDE_LIBRARY_REAL(local)) { @@ -2864,7 +2867,10 @@ void BKE_lib_override_library_update(Main *bmain, ID *local) * Not impossible to do, but would rather see first if extra useless usual user handling * is actually a (performances) issue here. */ - ID *tmp_id = BKE_id_copy(bmain, local->override_library->reference); + ID *tmp_id = BKE_id_copy_ex(bmain, + local->override_library->reference, + NULL, + LIB_ID_COPY_DEFAULT | LIB_ID_COPY_NO_LIB_OVERRIDE_LOCAL_DATA_FLAG); if (tmp_id == NULL) { return; @@ -2951,7 +2957,6 @@ void BKE_lib_override_library_update(Main *bmain, ID *local) DEG_relations_tag_update(bmain); } -/** Update all overrides from given \a bmain. */ void BKE_lib_override_library_main_update(Main *bmain) { ID *id; @@ -2972,7 +2977,6 @@ 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))) { @@ -3013,17 +3017,11 @@ bool BKE_lib_override_library_id_is_user_deletable(struct Main *bmain, struct ID * exact same data as "desired" ones (kind of "baked" data-blocks). */ -/** Initialize an override storage. */ OverrideLibraryStorage *BKE_lib_override_library_operations_store_init(void) { return BKE_main_new(); } -/** - * Generate suitable 'write' data (this only affects differential override operations). - * - * Note that \a local ID is no more modified by this call, - * all extra data are stored in its temp \a storage_id copy. */ ID *BKE_lib_override_library_operations_store_start(Main *bmain, OverrideLibraryStorage *override_storage, ID *local) @@ -3088,10 +3086,6 @@ ID *BKE_lib_override_library_operations_store_start(Main *bmain, return storage_id; } -/** - * Restore given ID modified by #BKE_lib_override_library_operations_store_start, to its - * original state. - */ void BKE_lib_override_library_operations_store_end( OverrideLibraryStorage *UNUSED(override_storage), ID *local) { diff --git a/source/blender/blenkernel/intern/lib_query.c b/source/blender/blenkernel/intern/lib_query.c index 2ac92828cec..1f20a84098c 100644 --- a/source/blender/blenkernel/intern/lib_query.c +++ b/source/blender/blenkernel/intern/lib_query.c @@ -76,48 +76,50 @@ typedef struct LibraryForeachIDData { BLI_LINKSTACK_DECLARE(ids_todo, ID *); } LibraryForeachIDData; -bool BKE_lib_query_foreachid_process(LibraryForeachIDData *data, ID **id_pp, int cb_flag) +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_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 +141,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 +160,20 @@ 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) +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 +189,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 +223,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 +241,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,6 +302,10 @@ 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; } @@ -292,43 +329,44 @@ 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 } -/** - * Loop over all of the ID's this data-block links to. - */ void BKE_library_foreach_ID_link( Main *bmain, ID *id, LibraryIDLinkCallback callback, void *user_data, int flag) { library_foreach_ID_link(bmain, NULL, id, callback, user_data, flag, NULL); } -/** - * re-usable function, use when replacing ID's - */ void BKE_library_update_ID_link_user(ID *id_dst, ID *id_src, const int cb_flag) { if (cb_flag & IDWALK_CB_USER) { @@ -340,12 +378,6 @@ void BKE_library_update_ID_link_user(ID *id_dst, ID *id_src, const int cb_flag) } } -/** - * Say whether given \a id_owner may use (in any way) a data-block of \a id_type_used. - * - * This is a 'simplified' abstract version of #BKE_library_foreach_ID_link() above, - * quite useful to reduce useless iterations in some cases. - */ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used) { /* any type of ID can be used in custom props. */ @@ -407,7 +439,7 @@ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used) case ID_LA: return (ELEM(id_type_used, ID_TE)); case ID_CA: - return ELEM(id_type_used, ID_OB); + return ELEM(id_type_used, ID_OB, ID_IM); case ID_KE: /* Warning! key->from, could be more types in future? */ return ELEM(id_type_used, ID_ME, ID_CU, ID_LT); @@ -517,16 +549,6 @@ static int foreach_libblock_id_users_callback(LibraryIDLinkCallbackData *cb_data return IDWALK_RET_NOP; } -/** - * Return the number of times given \a id_user uses/references \a id_used. - * - * \note This only checks for pointer references of an ID, shallow usages - * (like e.g. by RNA paths, as done for FCurves) are not detected at all. - * - * \param id_user: the ID which is supposed to use (reference) \a id_used. - * \param id_used: the ID which is supposed to be used (referenced) by \a id_user. - * \return the number of direct usages/references of \a id_used by \a id_user. - */ int BKE_library_ID_use_ID(ID *id_user, ID *id_used) { IDUsersIter iter; @@ -575,26 +597,16 @@ static bool library_ID_is_used(Main *bmain, void *idv, const bool check_linked) return is_defined; } -/** - * Check whether given ID is used locally (i.e. by another non-linked ID). - */ bool BKE_library_ID_is_locally_used(Main *bmain, void *idv) { return library_ID_is_used(bmain, idv, false); } -/** - * Check whether given ID is used indirectly (i.e. by another linked ID). - */ bool BKE_library_ID_is_indirectly_used(Main *bmain, void *idv) { return library_ID_is_used(bmain, idv, true); } -/** - * Combine #BKE_library_ID_is_locally_used() and #BKE_library_ID_is_indirectly_used() - * in a single call. - */ void BKE_library_ID_test_usages(Main *bmain, void *idv, bool *is_used_local, bool *is_used_linked) { IDUsersIter iter; @@ -709,21 +721,6 @@ static void lib_query_unused_ids_tag_recurse(Main *bmain, } } -/** - * Tag all unused IDs (a.k.a 'orphaned'). - * - * By default only tag IDs with `0` user count. - * If `do_tag_recursive` is set, it will check dependencies to detect all IDs that are not actually - * used in current file, including 'archipelagos` (i.e. set of IDs referencing each other in - * loops, but without any 'external' valid usages. - * - * 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. - */ void BKE_lib_query_unused_ids_tag(Main *bmain, const int tag, const bool do_local_ids, @@ -788,15 +785,6 @@ static int foreach_libblock_used_linked_data_tag_clear_cb(LibraryIDLinkCallbackD return IDWALK_RET_NOP; } -/** - * Detect orphaned linked data blocks (i.e. linked data not used (directly or indirectly) - * in any way by any local data), including complex cases like 'linked archipelagoes', i.e. - * linked data-blocks that use each other in loops, - * which prevents their deletion by 'basic' usage checks. - * - * \param do_init_tag: if \a true, all linked data are checked, if \a false, - * only linked data-blocks already tagged with #LIB_TAG_DOIT are checked. - */ void BKE_library_unused_linked_data_set_tag(Main *bmain, const bool do_init_tag) { ID *id; @@ -826,14 +814,6 @@ void BKE_library_unused_linked_data_set_tag(Main *bmain, const bool do_init_tag) } } -/** - * Untag linked data blocks used by other untagged linked data-blocks. - * Used to detect data-blocks that we can forcefully make local - * (instead of copying them to later get rid of original): - * All data-blocks we want to make local are tagged by caller, - * after this function has ran caller knows data-blocks still tagged can directly be made local, - * since they are only used by other data-blocks that will also be made fully local. - */ void BKE_library_indirectly_used_data_tag_clear(Main *bmain) { ListBase *lb_array[INDEX_ID_MAX]; diff --git a/source/blender/blenkernel/intern/lib_remap.c b/source/blender/blenkernel/intern/lib_remap.c index 48396c5e6d9..c3ccedb9608 100644 --- a/source/blender/blenkernel/intern/lib_remap.c +++ b/source/blender/blenkernel/intern/lib_remap.c @@ -91,6 +91,97 @@ enum { ID_REMAP_IS_USER_ONE_SKIPPED = 1 << 1, /* There was some skipped 'user_one' usages of old_id. */ }; +static void foreach_libblock_remap_callback_skip(const ID *id_owner, + ID **id_ptr, + IDRemap *id_remap_data, + const int cb_flag, + const bool is_indirect, + const bool is_reference, + const bool is_never_null, + const bool is_obj, + const bool is_obj_editmode) +{ + if (is_indirect) { + id_remap_data->skipped_indirect++; + if (is_obj) { + Object *ob = (Object *)id_owner; + if (ob->data == *id_ptr && ob->proxy != NULL) { + /* And another 'Proudly brought to you by Proxy Hell' hack! + * This will allow us to avoid clearing 'LIB_EXTERN' flag of obdata of proxies... */ + id_remap_data->skipped_direct++; + } + } + } + else if (is_never_null || is_obj_editmode || is_reference) { + id_remap_data->skipped_direct++; + } + else { + BLI_assert(0); + } + if (cb_flag & IDWALK_CB_USER) { + id_remap_data->skipped_refcounted++; + } + else if (cb_flag & IDWALK_CB_USER_ONE) { + /* No need to count number of times this happens, just a flag is enough. */ + id_remap_data->status |= ID_REMAP_IS_USER_ONE_SKIPPED; + } +} + +static void foreach_libblock_remap_callback_apply(ID *id_owner, + ID *id_self, + ID *old_id, + ID *new_id, + ID **id_ptr, + IDRemap *id_remap_data, + const int cb_flag, + const bool is_indirect, + const bool is_never_null, + const bool force_user_refcount, + const bool is_obj_proxy) +{ + if (!is_never_null) { + *id_ptr = new_id; + DEG_id_tag_update_ex(id_remap_data->bmain, + id_self, + ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + if (id_self != id_owner) { + DEG_id_tag_update_ex(id_remap_data->bmain, + id_owner, + ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + } + } + if (cb_flag & IDWALK_CB_USER) { + /* NOTE: by default we don't user-count IDs which are not in the main database. + * This is because in certain conditions we can have data-blocks in + * the main which are referencing data-blocks outside of it. + * For example, BKE_mesh_new_from_object() called on an evaluated + * object will cause such situation. + */ + if (force_user_refcount || (old_id->tag & LIB_TAG_NO_MAIN) == 0) { + id_us_min(old_id); + } + if (new_id != NULL && (force_user_refcount || (new_id->tag & LIB_TAG_NO_MAIN) == 0)) { + /* We do not want to handle LIB_TAG_INDIRECT/LIB_TAG_EXTERN here. */ + new_id->us++; + } + } + else if (cb_flag & IDWALK_CB_USER_ONE) { + id_us_ensure_real(new_id); + /* We cannot affect old_id->us directly, LIB_TAG_EXTRAUSER(_SET) + * are assumed to be set as needed, that extra user is processed in final handling. */ + } + if (!is_indirect || is_obj_proxy) { + id_remap_data->status |= ID_REMAP_IS_LINKED_DIRECT; + } + /* We need to remap proxy_from pointer of remapped proxy... sigh. */ + if (is_obj_proxy && new_id != NULL) { + Object *ob = (Object *)id_owner; + if (ob->proxy == (Object *)new_id) { + ob->proxy->proxy_from = ob; + } + } +} + static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) { const int cb_flag = cb_data->cb_flag; @@ -116,124 +207,82 @@ static int foreach_libblock_remap_callback(LibraryIDLinkCallbackData *cb_data) old_id = *id_p; } - if (*id_p && (*id_p == old_id)) { - /* Better remap to NULL than not remapping at all, - * then we can handle it as a regular remap-to-NULL case. */ - if ((cb_flag & IDWALK_CB_NEVER_SELF) && (new_id == id_self)) { - new_id = NULL; - } + /* Early exit when id pointer isn't set to an expected value. */ + if (*id_p == NULL || *id_p != old_id) { + return IDWALK_RET_NOP; + } + + /* Better remap to NULL than not remapping at all, + * then we can handle it as a regular remap-to-NULL case. */ + if ((cb_flag & IDWALK_CB_NEVER_SELF) && (new_id == id_self)) { + new_id = NULL; + } - 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, - * 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_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; - const bool skip_never_null = (id_remap_data->flag & ID_REMAP_SKIP_NEVER_NULL_USAGE) != 0; - const bool force_user_refcount = (id_remap_data->flag & ID_REMAP_FORCE_USER_REFCOUNT) != 0; + 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, + * 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) && + (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; + const bool skip_never_null = (id_remap_data->flag & ID_REMAP_SKIP_NEVER_NULL_USAGE) != 0; + const bool force_user_refcount = (id_remap_data->flag & ID_REMAP_FORCE_USER_REFCOUNT) != 0; #ifdef DEBUG_PRINT - printf( - "In %s (lib %p): Remapping %s (%p) to %s (%p) " - "(is_indirect: %d, skip_indirect: %d, is_reference: %d, skip_reference: %d)\n", - id->name, - id->lib, - old_id->name, - old_id, - new_id ? new_id->name : "<NONE>", - new_id, - is_indirect, - skip_indirect, - is_reference, - skip_reference); + printf( + "In %s (lib %p): Remapping %s (%p) to %s (%p) " + "(is_indirect: %d, skip_indirect: %d, is_reference: %d, skip_reference: %d)\n", + id->name, + id->lib, + old_id->name, + old_id, + new_id ? new_id->name : "<NONE>", + new_id, + is_indirect, + skip_indirect, + is_reference, + skip_reference); #endif - if ((id_remap_data->flag & ID_REMAP_FLAG_NEVER_NULL_USAGE) && - (cb_flag & IDWALK_CB_NEVER_NULL)) { - id_owner->tag |= LIB_TAG_DOIT; - } + if ((id_remap_data->flag & ID_REMAP_FLAG_NEVER_NULL_USAGE) && (cb_flag & IDWALK_CB_NEVER_NULL)) { + id_owner->tag |= LIB_TAG_DOIT; + } - /* Special hack in case it's Object->data and we are in edit mode, and new_id is not NULL - * (otherwise, we follow common NEVER_NULL flags). - * (skipped_indirect too). */ - if ((is_never_null && skip_never_null) || - (is_obj_editmode && (((Object *)id_owner)->data == *id_p) && new_id != NULL) || - (skip_indirect && is_indirect) || (is_reference && skip_reference)) { - if (is_indirect) { - id_remap_data->skipped_indirect++; - if (is_obj) { - Object *ob = (Object *)id_owner; - if (ob->data == *id_p && ob->proxy != NULL) { - /* And another 'Proudly brought to you by Proxy Hell' hack! - * This will allow us to avoid clearing 'LIB_EXTERN' flag of obdata of proxies... */ - id_remap_data->skipped_direct++; - } - } - } - else if (is_never_null || is_obj_editmode || is_reference) { - id_remap_data->skipped_direct++; - } - else { - BLI_assert(0); - } - if (cb_flag & IDWALK_CB_USER) { - id_remap_data->skipped_refcounted++; - } - else if (cb_flag & IDWALK_CB_USER_ONE) { - /* No need to count number of times this happens, just a flag is enough. */ - id_remap_data->status |= ID_REMAP_IS_USER_ONE_SKIPPED; - } - } - else { - if (!is_never_null) { - *id_p = new_id; - DEG_id_tag_update_ex(id_remap_data->bmain, - id_self, - ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - if (id_self != id_owner) { - DEG_id_tag_update_ex(id_remap_data->bmain, - id_owner, - ID_RECALC_COPY_ON_WRITE | ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); - } - } - if (cb_flag & IDWALK_CB_USER) { - /* NOTE: by default we don't user-count IDs which are not in the main database. - * This is because in certain conditions we can have data-blocks in - * the main which are referencing data-blocks outside of it. - * For example, BKE_mesh_new_from_object() called on an evaluated - * object will cause such situation. - */ - if (force_user_refcount || (old_id->tag & LIB_TAG_NO_MAIN) == 0) { - id_us_min(old_id); - } - if (new_id != NULL && (force_user_refcount || (new_id->tag & LIB_TAG_NO_MAIN) == 0)) { - /* We do not want to handle LIB_TAG_INDIRECT/LIB_TAG_EXTERN here. */ - new_id->us++; - } - } - else if (cb_flag & IDWALK_CB_USER_ONE) { - id_us_ensure_real(new_id); - /* We cannot affect old_id->us directly, LIB_TAG_EXTRAUSER(_SET) - * are assumed to be set as needed, that extra user is processed in final handling. */ - } - if (!is_indirect || is_obj_proxy) { - id_remap_data->status |= ID_REMAP_IS_LINKED_DIRECT; - } - /* We need to remap proxy_from pointer of remapped proxy... sigh. */ - if (is_obj_proxy && new_id != NULL) { - Object *ob = (Object *)id_owner; - if (ob->proxy == (Object *)new_id) { - ob->proxy->proxy_from = ob; - } - } - } + /* Special hack in case it's Object->data and we are in edit mode, and new_id is not NULL + * (otherwise, we follow common NEVER_NULL flags). + * (skipped_indirect too). */ + if ((is_never_null && skip_never_null) || + (is_obj_editmode && (((Object *)id_owner)->data == *id_p) && new_id != NULL) || + (skip_indirect && is_indirect) || (is_reference && skip_reference)) { + foreach_libblock_remap_callback_skip(id_owner, + id_p, + id_remap_data, + cb_flag, + is_indirect, + is_reference, + is_never_null, + is_obj, + is_obj_editmode); + } + else { + foreach_libblock_remap_callback_apply(id_owner, + id_self, + old_id, + new_id, + id_p, + id_remap_data, + cb_flag, + is_indirect, + is_never_null, + force_user_refcount, + is_obj_proxy); } return IDWALK_RET_NOP; @@ -281,6 +330,11 @@ static void libblock_remap_data_postprocess_object_update(Main *bmain, * to remove the NULL children from collections not used in any scene. */ BKE_collections_object_remove_nulls(bmain); } + else { + /* Remapping may have created duplicates of CollectionObject pointing to the same object within + * the same collection. */ + BKE_collections_object_remove_duplicates(bmain); + } BKE_main_collection_sync_remap(bmain); @@ -318,6 +372,7 @@ static void libblock_remap_data_postprocess_collection_update(Main *bmain, else { /* Temp safe fix, but a "tad" brute force... We should probably be able to use parents from * old_collection instead? */ + /* NOTE: Also takes care of duplicated child collections that remapping may have created. */ BKE_main_collections_parent_relations_rebuild(bmain); } @@ -345,7 +400,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, 0); + ntreeUpdateAllUsers(bmain, new_id); } /** @@ -455,15 +510,18 @@ static void libblock_remap_data( #endif } -/** - * Replace all references in given Main to \a old_id by \a new_id - * (if \a new_id is NULL, it unlinks \a old_id). - */ -void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const short remap_flags) +typedef struct LibblockRemapMultipleUserData { + Main *bmain; + short remap_flags; +} LibBlockRemapMultipleUserData; + +static void libblock_remap_foreach_idpair_cb(ID *old_id, ID *new_id, void *user_data) { + LibBlockRemapMultipleUserData *data = user_data; + Main *bmain = data->bmain; + const short remap_flags = data->remap_flags; + IDRemap id_remap_data; - ID *old_id = old_idv; - ID *new_id = new_idv; int skipped_direct, skipped_refcounted; BLI_assert(old_id != NULL); @@ -476,13 +534,6 @@ void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const free_notifier_reference_cb(old_id); } - /* We assume editors do not hold references to their IDs... This is false in some cases - * (Image is especially tricky here), - * editors' code is to handle refcount (id->us) itself then. */ - if (remap_editor_id_reference_cb) { - remap_editor_id_reference_cb(old_id, new_id); - } - skipped_direct = id_remap_data.skipped_direct; skipped_refcounted = id_remap_data.skipped_refcounted; @@ -555,6 +606,41 @@ void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const DEG_relations_tag_update(bmain); } +void BKE_libblock_remap_multiple_locked(Main *bmain, + const struct IDRemapper *mappings, + const short remap_flags) +{ + if (BKE_id_remapper_is_empty(mappings)) { + /* Early exit nothing to do. */ + return; + } + + LibBlockRemapMultipleUserData user_data; + user_data.bmain = bmain; + user_data.remap_flags = remap_flags; + BKE_id_remapper_iter(mappings, libblock_remap_foreach_idpair_cb, &user_data); + + /* We assume editors do not hold references to their IDs... This is false in some cases + * (Image is especially tricky here), + * editors' code is to handle refcount (id->us) itself then. */ + if (remap_editor_id_reference_cb) { + remap_editor_id_reference_cb(mappings); + } + + /* Full rebuild of DEG! */ + DEG_relations_tag_update(bmain); +} + +void BKE_libblock_remap_locked(Main *bmain, void *old_idv, void *new_idv, const short remap_flags) +{ + struct IDRemapper *remapper = BKE_id_remapper_create(); + ID *old_id = old_idv; + ID *new_id = new_idv; + BKE_id_remapper_add(remapper, old_id, new_id); + BKE_libblock_remap_multiple_locked(bmain, remapper, remap_flags); + BKE_id_remapper_free(remapper); +} + void BKE_libblock_remap(Main *bmain, void *old_idv, void *new_idv, const short remap_flags) { BKE_main_lock(bmain); @@ -564,13 +650,17 @@ void BKE_libblock_remap(Main *bmain, void *old_idv, void *new_idv, const short r BKE_main_unlock(bmain); } -/** - * Unlink given \a id from given \a bmain - * (does not touch to indirect, i.e. library, usages of the ID). - * - * \param do_flag_never_null: If true, all IDs using \a idv in a 'non-NULL' way are flagged by - * #LIB_TAG_DOIT flag (quite obviously, 'non-NULL' usages can never be unlinked by this function). - */ +void BKE_libblock_remap_multiple(Main *bmain, + const struct IDRemapper *mappings, + const short remap_flags) +{ + BKE_main_lock(bmain); + + BKE_libblock_remap_multiple_locked(bmain, mappings, remap_flags); + + BKE_main_unlock(bmain); +} + void BKE_libblock_unlink(Main *bmain, void *idv, const bool do_flag_never_null, @@ -586,16 +676,6 @@ void BKE_libblock_unlink(Main *bmain, BKE_main_unlock(bmain); } -/** - * Similar to libblock_remap, but only affects IDs used by given \a idv ID. - * - * \param old_idv: Unlike BKE_libblock_remap, can be NULL, - * in which case all ID usages by given \a idv will be cleared. - * \param us_min_never_null: If \a true and new_id is NULL, - * 'NEVER_NULL' ID usages keep their old id, but this one still gets its user count decremented - * (needed when given \a idv is going to be deleted right after being unlinked). - */ -/* Should be able to replace all _relink() funcs (constraints, rigidbody, etc.) ? */ /* XXX Arg! Naming... :( * _relink? avoids confusion with _remap, but is confusing with _unlink * _remap_used_ids? @@ -603,9 +683,13 @@ void BKE_libblock_unlink(Main *bmain, * BKE_id_remap maybe? * ... sigh */ + void BKE_libblock_relink_ex( Main *bmain, void *idv, void *old_idv, void *new_idv, const short remap_flags) { + + /* Should be able to replace all _relink() funcs (constraints, rigidbody, etc.) ? */ + ID *id = idv; ID *old_id = old_idv; ID *new_id = new_idv; @@ -669,88 +753,47 @@ 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; } -/** - * Similar to #libblock_relink_ex, - * but is remapping IDs to their newid value if non-NULL, in given \a id. - * - * Very specific usage, not sure we'll keep it on the long run, - * currently only used in Object/Collection duplication code... - * - * WARNING: This is a deprecated version of this function, should not be used by new code. See - * #BKE_libblock_relink_to_newid_new below. - */ -void BKE_libblock_relink_to_newid(ID *id) +static void libblock_relink_to_newid(Main *bmain, ID *id, const int remap_flag) { if (ID_IS_LINKED(id)) { return; } - BKE_library_foreach_ID_link(NULL, id, id_relink_to_newid_looper, NULL, 0); -} - -/* ************************ - * FIXME: Port all usages of #BKE_libblock_relink_to_newid to this - * #BKE_libblock_relink_to_newid_new new code and remove old one. - ************************** */ -static int id_relink_to_newid_looper_new(LibraryIDLinkCallbackData *cb_data) -{ - const int cb_flag = cb_data->cb_flag; - if (cb_flag & IDWALK_CB_EMBEDDED) { - return IDWALK_RET_NOP; - } - - Main *bmain = cb_data->bmain; - ID *id_owner = cb_data->id_owner; - ID **id_pointer = cb_data->id_pointer; - ID *id = *id_pointer; - if (id) { - /* See: NEW_ID macro */ - if (id->newid != NULL) { - BKE_libblock_relink_ex(bmain, id_owner, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE); - id = id->newid; - } - if (id->tag & LIB_TAG_NEW) { - id->tag &= ~LIB_TAG_NEW; - BKE_libblock_relink_to_newid_new(bmain, id); - } - } - return IDWALK_RET_NOP; + id->tag &= ~LIB_TAG_NEW; + BKE_library_foreach_ID_link( + bmain, id, id_relink_to_newid_looper, POINTER_FROM_INT(remap_flag), 0); } -/** - * Remaps ID usages of given ID to their `id->newid` pointer if not None, and proceeds recursively - * in the dependency tree of IDs for all data-blocks tagged with `LIB_TAG_NEW`. - * - * NOTE: `LIB_TAG_NEW` is cleared - * - * Very specific usage, not sure we'll keep it on the long run, - * currently only used in Object/Collection duplication code... - */ -void BKE_libblock_relink_to_newid_new(Main *bmain, ID *id) +void BKE_libblock_relink_to_newid(Main *bmain, ID *id, const int remap_flag) { if (ID_IS_LINKED(id)) { return; @@ -758,6 +801,8 @@ void BKE_libblock_relink_to_newid_new(Main *bmain, ID *id) /* We do not want to have those cached relationship data here. */ BLI_assert(bmain->relations == NULL); - id->tag &= ~LIB_TAG_NEW; - BKE_library_foreach_ID_link(bmain, id, id_relink_to_newid_looper_new, NULL, 0); + 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/lib_remap_test.cc b/source/blender/blenkernel/intern/lib_remap_test.cc new file mode 100644 index 00000000000..266ada3663d --- /dev/null +++ b/source/blender/blenkernel/intern/lib_remap_test.cc @@ -0,0 +1,369 @@ +/* + * 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) 2022 by Blender Foundation. + */ +#include "testing/testing.h" + +#include "BLI_utildefines.h" + +#include "CLG_log.h" + +#include "DNA_mesh_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "RNA_define.h" + +#include "BKE_appdir.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idtype.h" +#include "BKE_lib_id.h" +#include "BKE_lib_remap.h" +#include "BKE_main.h" +#include "BKE_mesh.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_scene.h" + +#include "IMB_imbuf.h" + +#include "ED_node.h" + +#include "MEM_guardedalloc.h" + +namespace blender::bke::tests { + +class TestData { + public: + Main *bmain = nullptr; + struct bContext *C = nullptr; + + virtual void setup() + { + if (bmain == nullptr) { + bmain = BKE_main_new(); + G.main = bmain; + } + + if (C == nullptr) { + C = CTX_create(); + CTX_data_main_set(C, bmain); + } + } + + virtual void teardown() + { + if (bmain != nullptr) { + BKE_main_free(bmain); + bmain = nullptr; + G.main = nullptr; + } + + if (C != nullptr) { + CTX_free(C); + C = nullptr; + } + } +}; + +class SceneTestData : public TestData { + public: + Scene *scene = nullptr; + void setup() override + { + TestData::setup(); + scene = BKE_scene_add(bmain, "IDRemapScene"); + CTX_data_scene_set(C, scene); + } +}; + +class CompositorTestData : public SceneTestData { + public: + bNodeTree *compositor_nodetree = nullptr; + void setup() override + { + SceneTestData::setup(); + ED_node_composit_default(C, scene); + compositor_nodetree = scene->nodetree; + } +}; + +class MeshTestData : public TestData { + public: + Mesh *mesh = nullptr; + + void setup() override + { + TestData::setup(); + mesh = BKE_mesh_add(bmain, nullptr); + } +}; + +class TwoMeshesTestData : public MeshTestData { + public: + Mesh *other_mesh = nullptr; + + void setup() override + { + MeshTestData::setup(); + other_mesh = BKE_mesh_add(bmain, nullptr); + } +}; + +class MeshObjectTestData : public MeshTestData { + public: + Object *object; + void setup() override + { + MeshTestData::setup(); + + object = BKE_object_add_only_object(bmain, OB_MESH, nullptr); + object->data = mesh; + } +}; + +template<typename TestData> class Context { + public: + TestData test_data; + + Context() + { + CLG_init(); + BKE_idtype_init(); + RNA_init(); + BKE_node_system_init(); + BKE_appdir_init(); + IMB_init(); + + test_data.setup(); + } + + ~Context() + { + test_data.teardown(); + + BKE_node_system_exit(); + RNA_exit(); + IMB_exit(); + BKE_appdir_exit(); + CLG_exit(); + } +}; + +/* -------------------------------------------------------------------- */ +/** \name Embedded IDs + * \{ */ + +TEST(lib_remap, embedded_ids_can_not_be_remapped) +{ + Context<CompositorTestData> context; + bNodeTree *other_tree = static_cast<bNodeTree *>(BKE_id_new_nomain(ID_NT, nullptr)); + + EXPECT_NE(context.test_data.scene, nullptr); + EXPECT_NE(context.test_data.compositor_nodetree, nullptr); + EXPECT_EQ(context.test_data.compositor_nodetree, context.test_data.scene->nodetree); + + BKE_libblock_remap( + context.test_data.bmain, context.test_data.compositor_nodetree, other_tree, 0); + + EXPECT_EQ(context.test_data.compositor_nodetree, context.test_data.scene->nodetree); + EXPECT_NE(context.test_data.scene->nodetree, other_tree); + + BKE_id_free(nullptr, other_tree); +} + +TEST(lib_remap, embedded_ids_can_not_be_deleted) +{ + Context<CompositorTestData> context; + + EXPECT_NE(context.test_data.scene, nullptr); + EXPECT_NE(context.test_data.compositor_nodetree, nullptr); + EXPECT_EQ(context.test_data.compositor_nodetree, context.test_data.scene->nodetree); + + BKE_libblock_remap(context.test_data.bmain, + context.test_data.compositor_nodetree, + nullptr, + ID_REMAP_SKIP_NEVER_NULL_USAGE); + + EXPECT_EQ(context.test_data.compositor_nodetree, context.test_data.scene->nodetree); + EXPECT_NE(context.test_data.scene->nodetree, nullptr); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Remap to self + * \{ */ + +TEST(lib_remap, delete_when_remap_to_self_not_allowed) +{ + Context<TwoMeshesTestData> context; + + EXPECT_NE(context.test_data.mesh, nullptr); + EXPECT_NE(context.test_data.other_mesh, nullptr); + context.test_data.mesh->texcomesh = context.test_data.other_mesh; + + BKE_libblock_remap( + context.test_data.bmain, context.test_data.other_mesh, context.test_data.mesh, 0); + + EXPECT_EQ(context.test_data.mesh->texcomesh, nullptr); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name User Reference Counting + * \{ */ + +TEST(lib_remap, users_are_decreased_when_not_skipping_never_null) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + EXPECT_EQ(context.test_data.mesh->id.us, 1); + + /* This is an invalid situation, test case tests this in between value until we have a better + * solution. */ + BKE_libblock_remap(context.test_data.bmain, context.test_data.mesh, nullptr, 0); + EXPECT_EQ(context.test_data.mesh->id.us, 0); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_NE(context.test_data.object->data, nullptr); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); +} + +TEST(lib_remap, users_are_same_when_skipping_never_null) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + EXPECT_EQ(context.test_data.mesh->id.us, 1); + + BKE_libblock_remap( + context.test_data.bmain, context.test_data.mesh, nullptr, ID_REMAP_SKIP_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.mesh->id.us, 1); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_NE(context.test_data.object->data, nullptr); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Never Null + * \{ */ + +TEST(lib_remap, do_not_delete_when_cannot_unset) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + + BKE_libblock_remap( + context.test_data.bmain, context.test_data.mesh, nullptr, ID_REMAP_SKIP_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_NE(context.test_data.object->data, nullptr); +} + +TEST(lib_remap, force_never_null_usage) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + + BKE_libblock_remap( + context.test_data.bmain, context.test_data.mesh, nullptr, ID_REMAP_FORCE_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, nullptr); +} + +TEST(lib_remap, never_null_usage_flag_not_requested_on_delete) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + + /* Never null usage isn't requested so the flag should not be set. */ + BKE_libblock_remap( + context.test_data.bmain, context.test_data.mesh, nullptr, ID_REMAP_SKIP_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_NE(context.test_data.object->data, nullptr); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); +} + +TEST(lib_remap, never_null_usage_flag_requested_on_delete) +{ + Context<MeshObjectTestData> context; + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + + /* Never null usage is requested so the flag should be set. */ + BKE_libblock_remap(context.test_data.bmain, + context.test_data.mesh, + nullptr, + ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_FLAG_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_NE(context.test_data.object->data, nullptr); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, LIB_TAG_DOIT); +} + +TEST(lib_remap, never_null_usage_flag_not_requested_on_remap) +{ + Context<MeshObjectTestData> context; + Mesh *other_mesh = BKE_mesh_add(context.test_data.bmain, nullptr); + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + + /* Never null usage isn't requested so the flag should not be set. */ + BKE_libblock_remap( + context.test_data.bmain, context.test_data.mesh, other_mesh, ID_REMAP_SKIP_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, other_mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); +} + +TEST(lib_remap, never_null_usage_flag_requested_on_remap) +{ + Context<MeshObjectTestData> context; + Mesh *other_mesh = BKE_mesh_add(context.test_data.bmain, nullptr); + + EXPECT_NE(context.test_data.object, nullptr); + EXPECT_EQ(context.test_data.object->data, context.test_data.mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, 0); + + /* Never null usage is requested so the flag should be set. */ + BKE_libblock_remap(context.test_data.bmain, + context.test_data.mesh, + other_mesh, + ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_FLAG_NEVER_NULL_USAGE); + EXPECT_EQ(context.test_data.object->data, other_mesh); + EXPECT_EQ(context.test_data.object->id.tag & LIB_TAG_DOIT, LIB_TAG_DOIT); +} + +/** \} */ + +} // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c index 36958e36004..c97b003d241 100644 --- a/source/blender/blenkernel/intern/library.c +++ b/source/blender/blenkernel/intern/library.c @@ -36,6 +36,7 @@ #include "BLT_translation.h" +#include "BKE_bpath.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_lib_query.h" @@ -57,7 +58,23 @@ 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); +} + +static void library_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Library *lib = (Library *)id; + + /* FIXME: Find if we should respect #BKE_BPATH_FOREACH_PATH_SKIP_PACKED here, and if not, explain + * why. */ + if (lib->packedfile != + NULL /*&& (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0 */) { + return; + } + + if (BKE_bpath_foreach_path_fixed_process(bpath_data, lib->filepath)) { + BKE_library_filepath_set(bpath_data->bmain, lib, lib->filepath); + } } IDTypeInfo IDType_ID_LI = { @@ -69,6 +86,7 @@ IDTypeInfo IDType_ID_LI = { .name_plural = "libraries", .translation_context = BLT_I18NCONTEXT_ID_LIBRARY, .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING | IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = NULL, .copy_data = NULL, @@ -76,6 +94,7 @@ IDTypeInfo IDType_ID_LI = { .make_local = NULL, .foreach_id = library_foreach_id, .foreach_cache = NULL, + .foreach_path = library_foreach_path, .owner_get = NULL, .blend_write = NULL, @@ -108,7 +127,7 @@ void BKE_library_filepath_set(Main *bmain, Library *lib, const char *filepath) */ /* 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. */ - const char *basepath = BKE_main_blendfile_path(bmain); - BLI_path_abs(lib->filepath_abs, basepath); + const char *blendfile_path = BKE_main_blendfile_path(bmain); + BLI_path_abs(lib->filepath_abs, blendfile_path); } } diff --git a/source/blender/blenkernel/intern/light.c b/source/blender/blenkernel/intern/light.c index a6150028f46..e73cda7e24d 100644 --- a/source/blender/blenkernel/intern/light.c +++ b/source/blender/blenkernel/intern/light.c @@ -129,7 +129,8 @@ 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)); } } @@ -194,6 +195,7 @@ IDTypeInfo IDType_ID_LA = { .name_plural = "lights", .translation_context = BLT_I18NCONTEXT_ID_LIGHT, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = light_init_data, .copy_data = light_copy_data, @@ -201,6 +203,7 @@ IDTypeInfo IDType_ID_LA = { .make_local = NULL, .foreach_id = light_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = light_blend_write, diff --git a/source/blender/blenkernel/intern/lightprobe.c b/source/blender/blenkernel/intern/lightprobe.c index 1f4abf36426..035e41815e5 100644 --- a/source/blender/blenkernel/intern/lightprobe.c +++ b/source/blender/blenkernel/intern/lightprobe.c @@ -53,8 +53,8 @@ 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) @@ -92,6 +92,7 @@ IDTypeInfo IDType_ID_LP = { .name_plural = "lightprobes", .translation_context = BLT_I18NCONTEXT_ID_LIGHTPROBE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = lightprobe_init_data, .copy_data = NULL, @@ -99,6 +100,7 @@ IDTypeInfo IDType_ID_LP = { .make_local = NULL, .foreach_id = lightprobe_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = lightprobe_blend_write, diff --git a/source/blender/blenkernel/intern/linestyle.c b/source/blender/blenkernel/intern/linestyle.c index f4e4dd9f1ab..95f41ab4b39 100644 --- a/source/blender/blenkernel/intern/linestyle.c +++ b/source/blender/blenkernel/intern/linestyle.c @@ -50,6 +50,7 @@ #include "BKE_linestyle.h" #include "BKE_main.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_texture.h" #include "BLO_read_write.h" @@ -155,12 +156,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 +171,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 +180,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 +189,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); } } } @@ -752,6 +755,7 @@ IDTypeInfo IDType_ID_LS = { .name_plural = "linestyles", .translation_context = BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = linestyle_init_data, .copy_data = linestyle_copy_data, @@ -759,6 +763,7 @@ IDTypeInfo IDType_ID_LS = { .make_local = NULL, .foreach_id = linestyle_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = linestyle_blend_write, @@ -1908,10 +1913,6 @@ int BKE_linestyle_geometry_modifier_remove(FreestyleLineStyle *linestyle, LineSt return 0; } -/** - * Reinsert \a modifier in modifier list with an offset of \a direction. - * \return if position of \a modifier has changed. - */ bool BKE_linestyle_color_modifier_move(FreestyleLineStyle *linestyle, LineStyleModifier *modifier, int direction) @@ -2085,5 +2086,5 @@ void BKE_linestyle_default_shader(const bContext *C, FreestyleLineStyle *linesty tosock = BLI_findlink(&output_linestyle->inputs, 0); /* Color */ nodeAddLink(ntree, input_texure, fromsock, output_linestyle, tosock); - ntreeUpdateTree(CTX_data_main(C), ntree); + BKE_ntree_update_main_tree(CTX_data_main(C), ntree, NULL); } diff --git a/source/blender/blenkernel/intern/main.c b/source/blender/blenkernel/intern/main.c index 9c3291edbcc..64731be57ac 100644 --- a/source/blender/blenkernel/intern/main.c +++ b/source/blender/blenkernel/intern/main.c @@ -205,6 +205,16 @@ void BKE_main_free(Main *mainvar) MEM_freeN(mainvar); } +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); @@ -267,7 +277,6 @@ static int main_relations_create_idlink_cb(LibraryIDLinkCallbackData *cb_data) return IDWALK_RET_NOP; } -/** Generate the mappings between used IDs and their users, and vice-versa. */ void BKE_main_relations_create(Main *bmain, const short flag) { if (bmain->relations != NULL) { @@ -315,9 +324,8 @@ 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) { @@ -339,12 +347,6 @@ void BKE_main_relations_tag_set(struct Main *bmain, BLI_ghashIterator_free(gh_iter); } -/** - * Create a GSet storing all IDs present in given \a bmain, by their pointers. - * - * \param gset: If not NULL, given GSet will be extended with IDs from given \a bmain, - * instead of creating a new one. - */ GSet *BKE_main_gset_create(Main *bmain, GSet *gset) { if (gset == NULL) { @@ -393,12 +395,6 @@ static bool lib_weak_key_cmp(const void *a, const void *b) 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( @@ -431,24 +427,11 @@ GHash *BKE_main_library_weak_reference_create(Main *bmain) 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) @@ -458,16 +441,6 @@ ID *BKE_main_library_weak_reference_search_item(GHash *library_weak_reference_ma 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, @@ -496,21 +469,6 @@ void BKE_main_library_weak_reference_add_item(GHash *library_weak_reference_mapp *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, @@ -534,16 +492,6 @@ void BKE_main_library_weak_reference_update_item(GHash *library_weak_reference_m *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, @@ -561,13 +509,6 @@ void BKE_main_library_weak_reference_remove_item(GHash *library_weak_reference_m MEM_SAFE_FREE(old_id->library_weak_reference); } -/** - * Generates a raw .blend file thumbnail data from given image. - * - * \param bmain: If not NULL, also store generated data in this Main. - * \param img: ImBuf image to generate thumbnail data from. - * \return The generated .blend file raw thumbnail data. - */ BlendThumbnail *BKE_main_thumbnail_from_imbuf(Main *bmain, ImBuf *img) { BlendThumbnail *data = NULL; @@ -592,13 +533,6 @@ BlendThumbnail *BKE_main_thumbnail_from_imbuf(Main *bmain, ImBuf *img) return data; } -/** - * Generates an image from raw .blend file thumbnail \a data. - * - * \param bmain: Use this bmain->blen_thumb data if given \a data is NULL. - * \param data: Raw .blend file thumbnail data. - * \return An ImBuf from given data, or NULL if invalid. - */ ImBuf *BKE_main_thumbnail_to_imbuf(Main *bmain, BlendThumbnail *data) { ImBuf *img = NULL; @@ -615,9 +549,6 @@ ImBuf *BKE_main_thumbnail_to_imbuf(Main *bmain, BlendThumbnail *data) return img; } -/** - * Generates an empty (black) thumbnail for given Main. - */ void BKE_main_thumbnail_create(struct Main *bmain) { MEM_SAFE_FREE(bmain->blen_thumb); @@ -627,28 +558,16 @@ void BKE_main_thumbnail_create(struct Main *bmain) bmain->blen_thumb->height = BLEN_THUMB_SIZE; } -/** - * Return filepath of given \a main. - */ const char *BKE_main_blendfile_path(const Main *bmain) { - return bmain->name; + return bmain->filepath; } -/** - * Return filepath of global main #G_MAIN. - * - * \warning Usage is not recommended, - * you should always try to get a valid Main pointer from context... - */ const char *BKE_main_blendfile_path_from_global(void) { return BKE_main_blendfile_path(G_MAIN); } -/** - * \return A pointer to the \a ListBase of given \a bmain for requested \a type ID type. - */ ListBase *which_libbase(Main *bmain, short type) { switch ((ID_Type)type) { @@ -736,18 +655,6 @@ ListBase *which_libbase(Main *bmain, short type) return NULL; } -/** - * Put the pointers to all the #ListBase structs in given `bmain` into the `*lb[INDEX_ID_MAX]` - * array, and return the number of those for convenience. - * - * This is useful for generic traversal of all the blocks in a #Main (by traversing all the lists - * in turn), without worrying about block types. - * - * \param lb: Array of lists #INDEX_ID_MAX in length. - * - * \note The order of each ID type #ListBase in the array is determined by the `INDEX_ID_<IDTYPE>` - * enum definitions in `DNA_ID.h`. See also the #FOREACH_MAIN_ID_BEGIN macro in `BKE_main.h` - */ int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/]) { /* Libraries may be accessed from pretty much any other ID. */ diff --git a/source/blender/blenkernel/intern/main_idmap.c b/source/blender/blenkernel/intern/main_idmap.c index c75365a788d..38523f22aad 100644 --- a/source/blender/blenkernel/intern/main_idmap.c +++ b/source/blender/blenkernel/intern/main_idmap.c @@ -88,18 +88,6 @@ static struct IDNameLib_TypeMap *main_idmap_from_idcode(struct IDNameLib_Map *id return NULL; } -/** - * Generate mapping from ID type/name to ID pointer for given \a bmain. - * - * \note When used during undo/redo, there is no guaranty that ID pointers from UI area - * are not pointing to freed memory (when some IDs have been deleted). To avoid crashes - * in those cases, one can provide the 'old' (aka current) Main database as reference. - * #BKE_main_idmap_lookup_id will then check that given ID does exist in \a old_bmain - * before trying to use it. - * - * \param create_valid_ids_set: If \a true, generate a reference to prevent freed memory accesses. - * \param old_bmain: If not NULL, its IDs will be added the valid references set. - */ struct IDNameLib_Map *BKE_main_idmap_create(struct Main *bmain, const bool create_valid_ids_set, struct Main *old_bmain, diff --git a/source/blender/blenkernel/intern/mask.c b/source/blender/blenkernel/intern/mask.c index 1d3ebaac303..12bbab57cf2 100644 --- a/source/blender/blenkernel/intern/mask.c +++ b/source/blender/blenkernel/intern/mask.c @@ -38,6 +38,7 @@ #include "BLT_translation.h" +#include "DNA_defaults.h" #include "DNA_mask_types.h" #include "BKE_animsys.h" @@ -255,6 +256,7 @@ IDTypeInfo IDType_ID_MSK = { .name_plural = "masks", .translation_context = BLT_I18NCONTEXT_ID_MASK, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = NULL, .copy_data = mask_copy_data, @@ -262,6 +264,7 @@ IDTypeInfo IDType_ID_MSK = { .make_local = NULL, .foreach_id = mask_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = mask_blend_write, @@ -371,7 +374,6 @@ MaskLayer *BKE_mask_layer_new(Mask *mask, const char *name) return masklay; } -/* NOTE: may still be hidden, caller needs to check. */ MaskLayer *BKE_mask_layer_active(Mask *mask) { return BLI_findlink(&mask->masklayers, mask->masklay_act); @@ -787,12 +789,11 @@ BLI_INLINE void orthogonal_direction_get(const float vec[2], float result[2]) normalize_v2(result); } -/* TODO(sergey): This function will re-calculate loads of stuff again and again - * when differentiating feather points. This might be easily cached - * in the callee function for this case. - */ void BKE_mask_point_normal(MaskSpline *spline, MaskSplinePoint *point, float u, float n[2]) { + /* TODO(sergey): This function will re-calculate loads of stuff again and again + * when differentiating feather points. This might be easily cached + * in the callee function for this case. */ MaskSplinePoint *point_prev, *point_next; @@ -1132,7 +1133,6 @@ MaskSpline *BKE_mask_spline_copy(const MaskSpline *spline) return nspline; } -/* NOTE: Does NOT add to the list. */ MaskLayerShape *BKE_mask_layer_shape_alloc(MaskLayer *masklay, const int frame) { MaskLayerShape *masklay_shape; @@ -1156,8 +1156,6 @@ void BKE_mask_layer_shape_free(MaskLayerShape *masklay_shape) MEM_freeN(masklay_shape); } -/** \brief Free all animation keys for a mask layer - */ void BKE_mask_layer_free_shapes(MaskLayer *masklay) { MaskLayerShape *masklay_shape; @@ -1245,7 +1243,6 @@ void BKE_mask_coord_from_image(Image *image, ImageUser *iuser, float r_co[2], co BKE_mask_coord_from_frame(r_co, co, frame_size); } -/* as above but divide */ void BKE_mask_coord_to_frame(float r_co[2], const float co[2], const float frame_size[2]) { if (frame_size[0] == frame_size[1]) { @@ -1312,7 +1309,7 @@ void BKE_mask_point_parent_matrix_get(MaskSplinePoint *point, MovieTrackingObject *ob = BKE_tracking_object_get_named(tracking, parent->parent); if (ob) { - MovieClipUser user = {0}; + MovieClipUser user = *DNA_struct_default_get(MovieClipUser); float clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, ctime); BKE_movieclip_user_set_frame(&user, ctime); @@ -1428,8 +1425,6 @@ void BKE_mask_get_handle_point_adjacent(MaskSpline *spline, *r_point_next = mask_spline_point_next(spline, points_array, point); } -/* calculates the tangent of a point by its previous and next - * (ignoring handles - as if its a poly line) */ void BKE_mask_calc_tangent_polyline(MaskSpline *spline, MaskSplinePoint *point, float t[2]) { float tvec_a[2], tvec_b[2]; @@ -1513,11 +1508,6 @@ void BKE_mask_calc_handle_adjacent_interp(MaskSpline *spline, } } -/** - * \brief Resets auto handles even for non-auto bezier points - * - * Useful for giving sane defaults. - */ void BKE_mask_calc_handle_point_auto(MaskSpline *spline, MaskSplinePoint *point, const bool do_recalc_length) @@ -1640,7 +1630,6 @@ static void mask_layer_shape_to_mask_point(BezTriple *bezt, bezt->radius = fp[7]; } -/* these functions match. copy is swapped */ void BKE_mask_layer_shape_from_mask(MaskLayer *masklay, MaskLayerShape *masklay_shape) { int tot = BKE_mask_layer_shape_totvert(masklay); @@ -1696,7 +1685,6 @@ BLI_INLINE void interp_v2_v2v2_flfl( target[1] = s * a[1] + t * b[1]; } -/* linear interpolation only */ void BKE_mask_layer_shape_to_mask_interp(MaskLayer *masklay, MaskLayerShape *masklay_shape_a, MaskLayerShape *masklay_shape_b, @@ -1757,9 +1745,6 @@ MaskLayerShape *BKE_mask_layer_shape_find_frame(MaskLayer *masklay, const int fr return NULL; } -/** - * When returning 2 - the frame isn't found but before/after frames are. - */ int BKE_mask_layer_shape_find_frame_range(MaskLayer *masklay, const float frame, MaskLayerShape **r_masklay_shape_a, @@ -1922,7 +1907,6 @@ static void interp_weights_uv_v2_apply(const float uv[2], r_pt[1] += dvec[0] * uv[1]; } -/* When a new points added - resize all shape-key array. */ void BKE_mask_layer_shape_changed_add(MaskLayer *masklay, int index, bool do_init, @@ -2017,7 +2001,6 @@ void BKE_mask_layer_shape_changed_add(MaskLayer *masklay, } } -/* move array to account for removed point */ void BKE_mask_layer_shape_changed_remove(MaskLayer *masklay, int index, int count) { MaskLayerShape *masklay_shape; @@ -2079,13 +2062,11 @@ static void mask_clipboard_free_ex(bool final_free) } } -/* Free the clipboard. */ void BKE_mask_clipboard_free(void) { mask_clipboard_free_ex(true); } -/* Copy selected visible splines from the given layer to clipboard. */ void BKE_mask_clipboard_copy_from_layer(MaskLayer *mask_layer) { MaskSpline *spline; @@ -2120,13 +2101,11 @@ void BKE_mask_clipboard_copy_from_layer(MaskLayer *mask_layer) } } -/* Check clipboard is empty. */ bool BKE_mask_clipboard_is_empty(void) { return BLI_listbase_is_empty(&mask_clipboard.splines); } -/* Paste the contents of clipboard to given mask layer */ void BKE_mask_clipboard_paste_to_layer(Main *bmain, MaskLayer *mask_layer) { MaskSpline *spline; diff --git a/source/blender/blenkernel/intern/mask_evaluate.c b/source/blender/blenkernel/intern/mask_evaluate.c index 4584d9e527e..69fc7554eed 100644 --- a/source/blender/blenkernel/intern/mask_evaluate.c +++ b/source/blender/blenkernel/intern/mask_evaluate.c @@ -720,10 +720,6 @@ static float (*mask_spline_feather_differentiated_points_with_resolution__double return feather; } -/** - * values align with #BKE_mask_spline_differentiate_with_resolution - * when \a resol arguments match. - */ float (*BKE_mask_spline_feather_differentiated_points_with_resolution( MaskSpline *spline, const unsigned int resol, @@ -788,7 +784,6 @@ float (*BKE_mask_spline_feather_points(MaskSpline *spline, int *r_tot_feather_po return feather; } -/* *** mask point functions which involve evaluation *** */ float *BKE_mask_point_segment_feather_diff(MaskSpline *spline, MaskSplinePoint *point, int width, diff --git a/source/blender/blenkernel/intern/mask_rasterize.c b/source/blender/blenkernel/intern/mask_rasterize.c index e04e5fceec6..b0876957620 100644 --- a/source/blender/blenkernel/intern/mask_rasterize.c +++ b/source/blender/blenkernel/intern/mask_rasterize.c @@ -151,7 +151,7 @@ BLI_INLINE unsigned int clampis_uint(const unsigned int v, } /* --------------------------------------------------------------------- */ -/* local structs for mask rasterizeing */ +/* local structs for mask rasterizing */ /* --------------------------------------------------------------------- */ /** @@ -1474,9 +1474,6 @@ static void maskrasterize_buffer_cb(void *__restrict userdata, } } -/** - * \brief Rasterize a buffer from a single mask (threaded execution). - */ void BKE_maskrasterize_buffer(MaskRasterHandle *mr_handle, const unsigned int width, const unsigned int height, diff --git a/source/blender/blenkernel/intern/material.c b/source/blender/blenkernel/intern/material.c index fa3fbd457d1..15469f910b4 100644 --- a/source/blender/blenkernel/intern/material.c +++ b/source/blender/blenkernel/intern/material.c @@ -63,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" @@ -76,6 +75,7 @@ #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" @@ -166,15 +166,14 @@ 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); } } @@ -262,6 +261,7 @@ IDTypeInfo IDType_ID_MA = { .name_plural = "materials", .translation_context = BLT_I18NCONTEXT_ID_MATERIAL, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = material_init_data, .copy_data = material_copy_data, @@ -269,6 +269,7 @@ IDTypeInfo IDType_ID_MA = { .make_local = NULL, .foreach_id = material_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = material_blend_write, @@ -388,7 +389,6 @@ short *BKE_object_material_len_p(Object *ob) return NULL; } -/* same as above but for ID's */ Material ***BKE_id_material_array_p(ID *id) { /* ensure we don't try get materials from non-obdata */ @@ -720,20 +720,14 @@ static ID *get_evaluated_object_data_with_materials(Object *ob) /* Meshes in edit mode need special handling. */ if (ob->type == OB_MESH && ob->mode == OB_MODE_EDIT) { Mesh *mesh = ob->data; - if (mesh->edit_mesh && mesh->edit_mesh->mesh_eval_final) { - data = &mesh->edit_mesh->mesh_eval_final->id; + Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(ob); + if (mesh->edit_mesh && editmesh_eval_final) { + data = &editmesh_eval_final->id; } } return data; } -/** - * On evaluated objects the number of materials on an object and its data might go out of sync. - * This is because during evaluation materials can be added/removed on the object data. - * - * For rendering or exporting we generally use the materials on the object data. However, some - * material indices might be overwritten by the object. - */ Material *BKE_object_material_get_eval(Object *ob, short act) { BLI_assert(DEG_is_evaluated_object(ob)); @@ -807,10 +801,6 @@ void BKE_id_material_eval_assign(ID *id, int slot, Material *material) (*materials_ptr)[slot_index] = material; } -/** - * Add an empty material slot if the id has no material slots. This material slot allows the - * material to be overwritten by object-linked materials. - */ void BKE_id_material_eval_ensure_default_slot(ID *id) { short *len_ptr = BKE_id_material_len_p(id); @@ -900,7 +890,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 losing materials in a local object when its linked obdata goes 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) @@ -1090,12 +1090,6 @@ void BKE_object_material_remap(Object *ob, const unsigned int *remap) } } -/** - * Calculate a material remapping from \a ob_src to \a ob_dst. - * - * \param remap_src_to_dst: An array the size of `ob_src->totcol` - * where index values are filled in which map to \a ob_dst materials. - */ void BKE_object_material_remap_calc(Object *ob_dst, Object *ob_src, short *remap_src_to_dst) { if (ob_src->totcol == 0) { @@ -1144,9 +1138,6 @@ void BKE_object_material_remap_calc(Object *ob_dst, Object *ob_src, short *remap BLI_ghash_free(gh_mat_map, NULL, NULL); } -/** - * Copy materials from evaluated geometry to the original geometry of an object. - */ void BKE_object_material_from_eval_data(Main *bmain, Object *ob_orig, ID *data_eval) { ID *data_orig = ob_orig->data; @@ -1181,7 +1172,6 @@ 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. */ void BKE_object_material_array_assign(Main *bmain, struct Object *ob, struct Material ***matar, @@ -1544,7 +1534,6 @@ bNode *BKE_texpaint_slot_material_find_node(Material *ma, short texpaint_slot) return find_data.r_node; } -/* r_col = current value, col = new value, (fac == 0) is no change */ void ramp_blend(int type, float r_col[3], const float fac, const float col[3]) { float tmp, facm = 1.0f - fac; diff --git a/source/blender/blenkernel/intern/mball.c b/source/blender/blenkernel/intern/mball.c index 6c8664aefed..ac6b0a04def 100644 --- a/source/blender/blenkernel/intern/mball.c +++ b/source/blender/blenkernel/intern/mball.c @@ -112,7 +112,7 @@ 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); } } @@ -189,6 +189,7 @@ IDTypeInfo IDType_ID_MB = { .name_plural = "metaballs", .translation_context = BLT_I18NCONTEXT_ID_METABALL, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = metaball_init_data, .copy_data = metaball_copy_data, @@ -196,6 +197,7 @@ IDTypeInfo IDType_ID_MB = { .make_local = NULL, .foreach_id = metaball_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = metaball_blend_write, @@ -219,8 +221,6 @@ MetaBall *BKE_mball_add(Main *bmain, const char *name) return mb; } -/* most simple meta-element adding function - * don't do context manipulation here (rna uses) */ MetaElem *BKE_mball_element_add(MetaBall *mb, const int type) { MetaElem *ml = MEM_callocN(sizeof(MetaElem), "metaelem"); @@ -267,13 +267,6 @@ MetaElem *BKE_mball_element_add(MetaBall *mb, const int type) return ml; } -/** - * Compute bounding box of all #MetaElem / #MetaBall - * - * Bounding box is computed from polygonized surface. \a ob is - * basic meta-balls (with name `Meta` for example). All other meta-ball objects - * (with names `Meta.001`, `Meta.002`, etc) are included in this bounding-box. - */ void BKE_mball_texspace_calc(Object *ob) { DispList *dl; @@ -317,7 +310,6 @@ void BKE_mball_texspace_calc(Object *ob) bb->flag &= ~BOUNDBOX_DIRTY; } -/** Return or compute bbox for given metaball object. */ BoundBox *BKE_mball_boundbox_get(Object *ob) { BLI_assert(ob->type == OB_MBALL); @@ -370,38 +362,29 @@ float *BKE_mball_make_orco(Object *ob, ListBase *dispbase) return orcodata; } -/** - * \brief Test, if \a ob is a basis meta-ball. - * - * It test last character of Object ID name. If last character - * is digit it return 0, else it return 1. - * - * - * Meta-Ball Basis Notes from Blender-2.5x - * ======================================= - * - * This is a can of worms. - * - * This really needs a rewrite/refactor its totally broken in anything other than basic cases - * Multiple Scenes + Set Scenes & mixing meta-ball basis _should_ work but fails to update the - * depsgraph on rename and linking into scenes or removal of basis meta-ball. - * So take care when changing this code. - * - * Main idiot thing here is that the system returns #BKE_mball_basis_find() - * objects which fail a #BKE_mball_is_basis() test. - * - * Not only that but the depsgraph and their areas depend on this behavior, - * so making small fixes here isn't worth it. - * - Campbell - */ bool BKE_mball_is_basis(Object *ob) { - /* just a quick test */ + /* Meta-Ball Basis Notes from Blender-2.5x + * ======================================= + * + * NOTE(@campbellbarton): This is a can of worms. + * + * This really needs a rewrite/refactor its totally broken in anything other than basic cases + * Multiple Scenes + Set Scenes & mixing meta-ball basis _should_ work but fails to update the + * depsgraph on rename and linking into scenes or removal of basis meta-ball. + * So take care when changing this code. + * + * Main idiot thing here is that the system returns #BKE_mball_basis_find() + * objects which fail a #BKE_mball_is_basis() test. + * + * Not only that but the depsgraph and their areas depend on this behavior, + * so making small fixes here isn't worth it. */ + + /* Just a quick test. */ const int len = strlen(ob->id.name); return (!isdigit(ob->id.name[len - 1])); } -/* return nonzero if ob1 is a basis mball for ob */ bool BKE_mball_is_basis_for(Object *ob1, Object *ob2) { int basis1nr, basis2nr; @@ -454,13 +437,6 @@ bool BKE_mball_is_any_unselected(const MetaBall *mb) return false; } -/** - * \brief copy some properties from object to other meta-ball object with same base name - * - * When some properties (wire-size, threshold, update flags) of meta-ball are changed, then this - * properties are copied to all meta-balls in same "group" (meta-balls with same base name: - * `MBall`, `MBall.001`, `MBall.002`, etc). The most important is to copy properties to the base - * meta-ball, because this meta-ball influence polygonization of meta-balls. */ void BKE_mball_properties_copy(Scene *scene, Object *active_object) { Scene *sce_iter = scene; @@ -499,14 +475,6 @@ void BKE_mball_properties_copy(Scene *scene, Object *active_object) } } -/** \brief This function finds the basis MetaBall. - * - * Basis meta-ball doesn't include any number at the end of - * its name. All meta-balls with same base of name can be - * blended. meta-balls with different basic name can't be blended. - * - * \warning #BKE_mball_is_basis() can fail on returned object, see function docs for details. - */ Object *BKE_mball_basis_find(Scene *scene, Object *object) { Object *bob = object; @@ -571,7 +539,6 @@ bool BKE_mball_minmax_ex( return changed; } -/* basic vertex data functions */ bool BKE_mball_minmax(const MetaBall *mb, float min[3], float max[3]) { INIT_MINMAX(min, max); @@ -646,7 +613,6 @@ void BKE_mball_translate(MetaBall *mb, const float offset[3]) } } -/* *** select funcs *** */ int BKE_mball_select_count(const MetaBall *mb) { int sel = 0; diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c index a2590171abd..eebe6efad78 100644 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ b/source/blender/blenkernel/intern/mball_tessellate.c @@ -55,6 +55,8 @@ /* experimental (faster) normal calculation */ // #define USE_ACCUM_NORMAL +#define MBALL_ARRAY_LEN_INIT 4096 + /* Data types */ typedef struct corner { /* corner of a cube */ @@ -448,7 +450,7 @@ static void make_face(PROCESS *process, int i1, int i2, int i3, int i4) #endif if (UNLIKELY(process->totindex == process->curindex)) { - process->totindex += 4096; + process->totindex = process->totindex ? (process->totindex * 2) : MBALL_ARRAY_LEN_INIT; process->indices = MEM_reallocN(process->indices, sizeof(int[4]) * process->totindex); } @@ -946,8 +948,8 @@ static int getedge(EDGELIST *table[], int i1, int j1, int k1, int i2, int j2, in */ static void addtovertices(PROCESS *process, const float v[3], const float no[3]) { - if (process->curvertex == process->totvertex) { - process->totvertex += 4096; + if (UNLIKELY(process->curvertex == process->totvertex)) { + process->totvertex = process->totvertex ? process->totvertex * 2 : MBALL_ARRAY_LEN_INIT; process->co = MEM_reallocN(process->co, process->totvertex * sizeof(float[3])); process->no = MEM_reallocN(process->no, process->totvertex * sizeof(float[3])); } @@ -1447,6 +1449,16 @@ void BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBa /* add resulting surface to displist */ if (process.curindex) { + + /* Avoid over-allocation since this is stored in the displist. */ + if (process.curindex != process.totindex) { + process.indices = MEM_reallocN(process.indices, sizeof(int[4]) * process.curindex); + } + if (process.curvertex != process.totvertex) { + process.co = MEM_reallocN(process.co, process.curvertex * sizeof(float[3])); + process.no = MEM_reallocN(process.no, process.curvertex * sizeof(float[3])); + } + dl = MEM_callocN(sizeof(DispList), "mballdisp"); BLI_addtail(dispbase, dl); dl->type = DL_INDEX4; diff --git a/source/blender/blenkernel/intern/mesh.c b/source/blender/blenkernel/intern/mesh.cc index ed3766ad6a3..73fe279552d 100644 --- a/source/blender/blenkernel/intern/mesh.c +++ b/source/blender/blenkernel/intern/mesh.cc @@ -38,16 +38,20 @@ #include "BLI_endian_switch.h" #include "BLI_ghash.h" #include "BLI_hash.h" +#include "BLI_index_range.hh" #include "BLI_linklist.h" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_memarena.h" #include "BLI_string.h" +#include "BLI_task.hh" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "BKE_anim_data.h" +#include "BKE_bpath.h" #include "BKE_deform.h" #include "BKE_editmesh.h" #include "BKE_global.h" @@ -88,7 +92,11 @@ static void mesh_init_data(ID *id) CustomData_reset(&mesh->pdata); CustomData_reset(&mesh->ldata); - BKE_mesh_runtime_reset(mesh); + BKE_mesh_runtime_init_data(mesh); + + /* A newly created mesh does not have normals, so tag them dirty. This will be cleared + * by #BKE_mesh_vertex_normals_clear_dirty or #BKE_mesh_poly_normals_ensure. */ + BKE_mesh_normals_tag_dirty(mesh); mesh->face_sets_color_seed = BLI_hash_int(PIL_check_seconds_timer_i() & UINT_MAX); } @@ -124,7 +132,7 @@ 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); @@ -142,9 +150,18 @@ 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->cd_flag = mesh_src->cd_flag; + + mesh_dst->edit_mesh = nullptr; - mesh_dst->mselect = MEM_dupallocN(mesh_dst->mselect); + mesh_dst->mselect = (MSelect *)MEM_dupallocN(mesh_dst->mselect); + + /* Set normal layers dirty, since they aren't included in CD_MASK_MESH and are therefore not + * copied to the destination mesh. Alternatively normal layers could be copied if they aren't + * dirty, avoiding recomputation in some cases. However, a copied mesh is often changed anyway, + * so that idea is not clearly better. With proper reference counting, all custom data layers + * could be copied as the cost would be much lower. */ + BKE_mesh_normals_tag_dirty(mesh_dst); /* TODO: Do we want to add flag to prevent this? */ if (mesh_src->key && (flag & LIB_ID_COPY_SHAPEKEY)) { @@ -152,6 +169,21 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int /* XXX This is not nice, we need to make BKE_id_copy_ex fully re-entrant... */ mesh_dst->key->from = &mesh_dst->id; } + + BKE_mesh_assert_normals_dirty_or_calculated(mesh_dst); +} + +void BKE_mesh_free_editmesh(struct Mesh *mesh) +{ + if (mesh->edit_mesh == nullptr) { + return; + } + + if (mesh->edit_mesh->is_shallow_copy == false) { + BKE_editmesh_free_data(mesh->edit_mesh); + } + MEM_freeN(mesh->edit_mesh); + mesh->edit_mesh = nullptr; } static void mesh_free_data(ID *id) @@ -160,15 +192,9 @@ static void mesh_free_data(ID *id) BLI_freelistN(&mesh->vertex_group_names); - if (mesh->edit_mesh) { - if (mesh->edit_mesh->is_shallow_copy == false) { - BKE_editmesh_free_data(mesh->edit_mesh); - } - MEM_freeN(mesh->edit_mesh); - mesh->edit_mesh = NULL; - } + BKE_mesh_free_editmesh(mesh); - BKE_mesh_runtime_clear_cache(mesh); + BKE_mesh_runtime_free_data(mesh); mesh_clear_geometry(mesh); MEM_SAFE_FREE(mesh->mat); } @@ -176,10 +202,18 @@ 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); + } +} + +static void mesh_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Mesh *me = (Mesh *)id; + if (me->ldata.external) { + BKE_bpath_foreach_path_fixed_process(bpath_data, me->ldata.external->filename); } } @@ -188,14 +222,14 @@ 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); - 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]; + 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]; /* cache only - don't write */ - mesh->mface = NULL; + mesh->mface = nullptr; mesh->totface = 0; memset(&mesh->fdata, 0, sizeof(mesh->fdata)); memset(&mesh->runtime, 0, sizeof(mesh->runtime)); @@ -203,22 +237,22 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address /* 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->mvert = nullptr; mesh->totvert = 0; memset(&mesh->vdata, 0, sizeof(mesh->vdata)); vlayers = vlayers_buff; - mesh->medge = NULL; + mesh->medge = nullptr; mesh->totedge = 0; memset(&mesh->edata, 0, sizeof(mesh->edata)); elayers = elayers_buff; - mesh->mloop = NULL; + mesh->mloop = nullptr; mesh->totloop = 0; memset(&mesh->ldata, 0, sizeof(mesh->ldata)); llayers = llayers_buff; - mesh->mpoly = NULL; + mesh->mpoly = nullptr; mesh->totpoly = 0; memset(&mesh->pdata, 0, sizeof(mesh->pdata)); players = players_buff; @@ -307,11 +341,13 @@ 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; - BKE_mesh_runtime_reset(mesh); + mesh->edit_mesh = nullptr; + + memset(&mesh->runtime, 0, sizeof(mesh->runtime)); + BKE_mesh_runtime_init_data(mesh); /* happens with old files */ - if (mesh->mselect == NULL) { + if (mesh->mselect == nullptr) { mesh->totselect = 0; } @@ -321,6 +357,10 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id) BLI_endian_switch_uint32_array(tf->col, 4); } } + + /* We don't expect to load normals from files, since they are derived data. */ + BKE_mesh_normals_tag_dirty(mesh); + BKE_mesh_assert_normals_dirty_or_calculated(mesh); } static void mesh_blend_read_lib(BlendLibReader *reader, ID *id) @@ -353,31 +393,33 @@ 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 = IDTYPE_FLAGS_APPEND_IS_REUSABLE, - - .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_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 */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, + + /* init_data */ mesh_init_data, + /* copy_data */ mesh_copy_data, + /* free_data */ mesh_free_data, + /* make_local */ nullptr, + /* foreach_id */ mesh_foreach_id, + /* foreach_cache */ nullptr, + /* foreach_path */ mesh_foreach_path, + /* owner_get */ nullptr, + + /* 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 */ nullptr, + + /* lib_override_apply_post */ nullptr, }; enum { @@ -439,13 +481,15 @@ static int customdata_compare( 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) { + l1 = &c1->layers[i]; + if ((CD_TYPE_AS_MASK(l1->type) & cd_mask_all_attr) && l1->anonymous_id == nullptr) { layer_count1++; } } for (int i = 0; i < c2->totlayer; i++) { - if (CD_TYPE_AS_MASK(c2->layers[i].type) & cd_mask_all_attr) { + l2 = &c2->layers[i]; + if ((CD_TYPE_AS_MASK(l2->type) & cd_mask_all_attr) && l2->anonymous_id == nullptr) { layer_count2++; } } @@ -461,7 +505,8 @@ static int customdata_compare( 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)) { + if (l1->type != l2->type || !STREQ(l1->name, l2->name) || l1->anonymous_id != nullptr || + l2->anonymous_id != nullptr) { continue; } /* At this point `l1` and `l2` have the same name and type, so they should be compared. */ @@ -469,8 +514,8 @@ static int customdata_compare( switch (l1->type) { case CD_MVERT: { - MVert *v1 = l1->data; - MVert *v2 = l2->data; + MVert *v1 = (MVert *)l1->data; + MVert *v2 = (MVert *)l2->data; int vtot = m1->totvert; for (j = 0; j < vtot; j++, v1++, v2++) { @@ -486,8 +531,8 @@ static int customdata_compare( /* We're order-agnostic for edges here. */ case CD_MEDGE: { - MEdge *e1 = l1->data; - MEdge *e2 = l2->data; + MEdge *e1 = (MEdge *)l1->data; + MEdge *e2 = (MEdge *)l2->data; int etot = m1->totedge; EdgeHash *eh = BLI_edgehash_new_ex(__func__, etot); @@ -500,12 +545,12 @@ static int customdata_compare( return MESHCMP_EDGEUNKNOWN; } } - BLI_edgehash_free(eh, NULL); + BLI_edgehash_free(eh, nullptr); break; } case CD_MPOLY: { - MPoly *p1 = l1->data; - MPoly *p2 = l2->data; + MPoly *p1 = (MPoly *)l1->data; + MPoly *p2 = (MPoly *)l2->data; int ptot = m1->totpoly; for (j = 0; j < ptot; j++, p1++, p2++) { @@ -528,8 +573,8 @@ static int customdata_compare( break; } case CD_MLOOP: { - MLoop *lp1 = l1->data; - MLoop *lp2 = l2->data; + MLoop *lp1 = (MLoop *)l1->data; + MLoop *lp2 = (MLoop *)l2->data; int ltot = m1->totloop; for (j = 0; j < ltot; j++, lp1++, lp2++) { @@ -540,8 +585,8 @@ static int customdata_compare( break; } case CD_MLOOPUV: { - MLoopUV *lp1 = l1->data; - MLoopUV *lp2 = l2->data; + MLoopUV *lp1 = (MLoopUV *)l1->data; + MLoopUV *lp2 = (MLoopUV *)l2->data; int ltot = m1->totloop; for (j = 0; j < ltot; j++, lp1++, lp2++) { @@ -552,8 +597,8 @@ static int customdata_compare( break; } case CD_MLOOPCOL: { - MLoopCol *lp1 = l1->data; - MLoopCol *lp2 = l2->data; + MLoopCol *lp1 = (MLoopCol *)l1->data; + MLoopCol *lp2 = (MLoopCol *)l2->data; int ltot = m1->totloop; for (j = 0; j < ltot; j++, lp1++, lp2++) { @@ -564,8 +609,8 @@ static int customdata_compare( break; } case CD_MDEFORMVERT: { - MDeformVert *dv1 = l1->data; - MDeformVert *dv2 = l2->data; + MDeformVert *dv1 = (MDeformVert *)l1->data; + MDeformVert *dv2 = (MDeformVert *)l2->data; int dvtot = m1->totvert; for (j = 0; j < dvtot; j++, dv1++, dv2++) { @@ -588,8 +633,8 @@ static int customdata_compare( break; } case CD_PROP_FLOAT: { - const float *l1_data = l1->data; - const float *l2_data = l2->data; + 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)) { @@ -599,8 +644,8 @@ static int customdata_compare( break; } case CD_PROP_FLOAT2: { - const float(*l1_data)[2] = l1->data; - const float(*l2_data)[2] = l2->data; + 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)) { @@ -613,8 +658,8 @@ static int customdata_compare( break; } case CD_PROP_FLOAT3: { - const float(*l1_data)[3] = l1->data; - const float(*l2_data)[3] = l2->data; + 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)) { @@ -630,8 +675,8 @@ static int customdata_compare( break; } case CD_PROP_INT32: { - const int *l1_data = l1->data; - const int *l2_data = l2->data; + 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]) { @@ -641,8 +686,8 @@ static int customdata_compare( break; } case CD_PROP_BOOL: { - const bool *l1_data = l1->data; - const bool *l2_data = l2->data; + 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]) { @@ -652,8 +697,8 @@ static int customdata_compare( break; } case CD_PROP_COLOR: { - const MPropCol *l1_data = l1->data; - const MPropCol *l2_data = l2->data; + 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++) { @@ -674,12 +719,6 @@ static int customdata_compare( return 0; } -/** - * Used for unit testing; compares two meshes, checking only - * differences we care about. should be usable with leaf's - * testing framework I get RNA work done, will use hackish - * testing code for now. - */ const char *BKE_mesh_cmp(Mesh *me1, Mesh *me2, float thresh) { int c; @@ -720,7 +759,7 @@ const char *BKE_mesh_cmp(Mesh *me1, Mesh *me2, float thresh) return cmpcode_to_str(c); } - return NULL; + return nullptr; } static void mesh_ensure_tessellation_customdata(Mesh *me) @@ -765,7 +804,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) { @@ -777,7 +816,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; } @@ -785,7 +824,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) { @@ -797,7 +837,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)) { @@ -807,7 +847,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; } } @@ -816,7 +856,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)) { @@ -834,12 +874,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) { @@ -854,20 +894,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) @@ -879,10 +919,6 @@ 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). - * Only use for undo, in most cases `BKE_id_free(NULL, me)` should be used. - */ void BKE_mesh_free_data_for_undo(Mesh *me) { mesh_free_data(&me->id); @@ -937,15 +973,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; } @@ -954,28 +990,28 @@ 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. */ @@ -997,10 +1033,6 @@ Mesh *BKE_mesh_new_nomain( return mesh; } -/** - * Copy user editable settings that we want to preserve - * when a new mesh is based on an existing mesh. - */ void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src) { /* Copy general settings. */ @@ -1023,12 +1055,6 @@ void BKE_mesh_copy_parameters(Mesh *me_dst, const Mesh *me_src) me_dst->vertex_group_active_index = me_src->vertex_group_active_index; } -/** - * A version of #BKE_mesh_copy_parameters that is intended for evaluated output - * (the modifier stack for example). - * - * \warning User counts are not handled for ID's. - */ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src) { /* User counts aren't handled, don't copy into a mesh from #G_MAIN. */ @@ -1036,15 +1062,17 @@ void BKE_mesh_copy_parameters_for_eval(Mesh *me_dst, const Mesh *me_src) BKE_mesh_copy_parameters(me_dst, me_src); + BKE_mesh_assert_normals_dirty_or_calculated(me_dst); + /* 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; } @@ -1059,9 +1087,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; @@ -1083,6 +1111,18 @@ Mesh *BKE_mesh_new_nomain_from_template_ex(const Mesh *me_src, mesh_tessface_clear_intern(me_dst, false); } + me_dst->runtime.cd_dirty_poly = me_src->runtime.cd_dirty_poly; + me_dst->runtime.cd_dirty_vert = me_src->runtime.cd_dirty_vert; + + /* Ensure that when no normal layers exist, they are marked dirty, because + * normals might not have been included in the mask of copied layers. */ + if (!CustomData_has_layer(&me_dst->vdata, CD_NORMAL)) { + me_dst->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + if (!CustomData_has_layer(&me_dst->pdata, CD_NORMAL)) { + me_dst->runtime.cd_dirty_poly |= CD_MASK_NORMAL; + } + /* The destination mesh should at least have valid primary CD layers, * even in cases where the source mesh does not. */ mesh_ensure_cdlayers_primary(me_dst, do_tessface); @@ -1105,7 +1145,7 @@ 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; + mesh_eval->edit_mesh = nullptr; mesh_free_data(&mesh_eval->id); BKE_libblock_free_data(&mesh_eval->id, false); MEM_freeN(mesh_eval); @@ -1119,7 +1159,7 @@ Mesh *BKE_mesh_copy_for_eval(const 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; } @@ -1140,14 +1180,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, @@ -1155,8 +1193,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; } @@ -1165,7 +1203,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; @@ -1175,8 +1213,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); @@ -1185,8 +1223,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; @@ -1251,17 +1289,17 @@ void BKE_mesh_texspace_get(Mesh *me, float r_loc[3], float r_size[3]) } } -void BKE_mesh_texspace_get_reference(Mesh *me, short **r_texflag, float **r_loc, float **r_size) +void BKE_mesh_texspace_get_reference(Mesh *me, char **r_texflag, float **r_loc, float **r_size) { 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; } } @@ -1269,7 +1307,7 @@ void BKE_mesh_texspace_get_reference(Mesh *me, short **r_texflag, float **r_loc, void BKE_mesh_texspace_copy_from_object(Mesh *me, Object *ob) { float *texloc, *texsize; - short *texflag; + char *texflag; if (BKE_object_obdata_texspace_get(ob, &texflag, &texloc, &texsize)) { me->texflag = *texflag; @@ -1280,11 +1318,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); @@ -1317,10 +1355,18 @@ void BKE_mesh_orco_verts_transform(Mesh *me, float (*orco)[3], int totvert, int } } -/** - * Rotates the vertices of a face in case v[2] or v[3] (vertex index) is = 0. - * this is necessary to make the if #MFace.v4 check for quads work. - */ +void BKE_mesh_orco_ensure(Object *ob, Mesh *mesh) +{ + if (CustomData_has_layer(&mesh->vdata, CD_ORCO)) { + return; + } + + /* Orcos are stored in normalized 0..1 range by convention. */ + float(*orcodata)[3] = BKE_mesh_orco_verts_get(ob); + BKE_mesh_orco_verts_transform(mesh, orcodata, mesh->totvert, false); + CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_ASSIGN, orcodata, mesh->totvert); +} + int BKE_mesh_mface_index_validate(MFace *mface, CustomData *fdata, int mfindex, int nr) { /* first test if the face is legal */ @@ -1391,28 +1437,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); } @@ -1524,10 +1570,6 @@ void BKE_mesh_smooth_flag_set(Mesh *me, const bool use_smooth) } } -/** - * Find the index of the loop in 'poly' which references vertex, - * returns -1 if not found - */ int poly_find_loop_from_vert(const MPoly *poly, const MLoop *loopstart, uint vert) { for (int j = 0; j < poly->totloop; j++, loopstart++) { @@ -1539,11 +1581,6 @@ int poly_find_loop_from_vert(const MPoly *poly, const MLoop *loopstart, uint ver return -1; } -/** - * Fill \a r_adj with the loop indices in \a poly adjacent to the - * vertex. Returns the index of the loop matching vertex, or -1 if the - * vertex is not in \a poly - */ int poly_get_adj_loops_from_vert(const MPoly *poly, const MLoop *mloop, uint vert, uint r_adj[2]) { int corner = poly_find_loop_from_vert(poly, &mloop[poly->loopstart], vert); @@ -1557,10 +1594,6 @@ int poly_get_adj_loops_from_vert(const MPoly *poly, const MLoop *mloop, uint ver return corner; } -/** - * Return the index of the edge vert that is not equal to \a v. If - * neither edge vertex is equal to \a v, returns -1. - */ int BKE_mesh_edge_other_vert(const MEdge *e, int v) { if (e->v1 == v) { @@ -1573,9 +1606,6 @@ int BKE_mesh_edge_other_vert(const MEdge *e, int v) return -1; } -/** - * Sets each output array element to the edge index if it is a real edge, or -1. - */ void BKE_mesh_looptri_get_real_edges(const Mesh *mesh, const MLoopTri *looptri, int r_edges[3]) { for (int i = 2, i_next = 0; i_next < 3; i = i_next++) { @@ -1588,23 +1618,45 @@ void BKE_mesh_looptri_get_real_edges(const Mesh *mesh, const MLoopTri *looptri, } } -/* basic vertex data functions */ bool BKE_mesh_minmax(const Mesh *me, float r_min[3], float r_max[3]) { - int i = me->totvert; - MVert *mvert; - for (mvert = me->mvert; i--; mvert++) { - minmax_v3v3_v3(r_min, r_max, mvert->co); + using namespace blender; + if (me->totvert == 0) { + return false; } - return (me->totvert != 0); + struct Result { + float3 min; + float3 max; + }; + + const Result minmax = threading::parallel_reduce( + IndexRange(me->totvert), + 1024, + Result{float3(FLT_MAX), float3(-FLT_MAX)}, + [&](IndexRange range, const Result &init) { + Result result = init; + for (const int i : range) { + math::min_max(float3(me->mvert[i].co), result.min, result.max); + } + return result; + }, + [](const Result &a, const Result &b) { + return Result{math::min(a.min, b.min), math::max(a.max, b.max)}; + }); + + copy_v3_v3(r_min, math::min(minmax.min, float3(r_min))); + copy_v3_v3(r_max, math::max(minmax.max, float3(r_max))); + + return true; } 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 layer has been re-allocated need to update pointers stored in the mesh. */ BKE_mesh_update_customdata_pointers(me, false); @@ -1614,9 +1666,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); } @@ -1649,9 +1700,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); } @@ -1723,7 +1773,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; @@ -1760,19 +1811,16 @@ 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; me->mselect = mselect_dst; } -/** - * Return the index within me->mselect, or -1 - */ int BKE_mesh_mselect_find(Mesh *me, int index, int type) { BLI_assert(ELEM(type, ME_VSEL, ME_ESEL, ME_FSEL)); @@ -1786,9 +1834,6 @@ int BKE_mesh_mselect_find(Mesh *me, int index, int type) return -1; } -/** - * Return The index of the active element. - */ int BKE_mesh_mselect_active_get(Mesh *me, int type) { BLI_assert(ELEM(type, ME_VSEL, ME_ESEL, ME_FSEL)); @@ -1807,7 +1852,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++; @@ -1843,7 +1888,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; @@ -1854,7 +1899,8 @@ 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]); @@ -1867,7 +1913,8 @@ 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]); @@ -1875,69 +1922,33 @@ void BKE_mesh_vert_coords_apply_with_mat4(Mesh *mesh, 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); - mesh->mvert = mv; - for (int i = 0; i < mesh->totvert; i++, mv++) { - copy_v3_v3_short(mv->no, vert_normals[i]); - } - mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL; -} - -/** - * Compute 'split' (aka loop, or per face corner's) normals. - * - * \param r_lnors_spacearr: Allows to get computed loop normal space array. - * That data, among other things, contains 'smooth fan' info, useful e.g. - * to split geometry along sharp edges... - */ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spacearr) { float(*r_loopnors)[3]; - float(*polynors)[3]; - short(*clnors)[2] = NULL; - bool free_polynors = false; + short(*clnors)[2] = nullptr; /* 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); - - 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); - free_polynors = false; - } - else { - polynors = 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, - NULL); - free_polynors = true; - } + /* may be nullptr */ + clnors = (short(*)[2])CustomData_get_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); BKE_mesh_normals_loop_split(mesh->mvert, + BKE_mesh_vertex_normals_ensure(mesh), mesh->totvert, mesh->medge, mesh->totedge, @@ -1945,48 +1956,44 @@ void BKE_mesh_calc_normals_split_ex(Mesh *mesh, MLoopNorSpaceArray *r_lnors_spac r_loopnors, mesh->totloop, mesh->mpoly, - (const float(*)[3])polynors, + BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly, use_split_normals, split_angle, r_lnors_spacearr, clnors, - NULL); + nullptr); - if (free_polynors) { - MEM_freeN(polynors); - } + BKE_mesh_assert_normals_dirty_or_calculated(mesh); - 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. */ -static int split_faces_prepare_new_verts(const Mesh *mesh, +static int split_faces_prepare_new_verts(Mesh *mesh, MLoopNorSpaceArray *lnors_spacearr, SplitFaceNewVert **new_verts, MemArena *memarena) @@ -1994,12 +2001,13 @@ 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; - MVert *mvert = mesh->mvert; MLoop *mloop = mesh->mloop; + BKE_mesh_vertex_normals_ensure(mesh); + float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(mesh); BLI_bitmap *verts_used = BLI_BITMAP_NEW(verts_len, __func__); BLI_bitmap *done_loops = BLI_BITMAP_NEW(loops_len, __func__); @@ -2043,11 +2051,12 @@ static int split_faces_prepare_new_verts(const Mesh *mesh, * vnor should always be defined to 'automatic normal' value computed from its polys, * not some custom normal. * Fortunately, that's the loop normal space's 'lnor' reference vector. ;) */ - normal_float_to_short_v3(mvert[vert_idx].no, (*lnor_space)->vec_lnor); + copy_v3_v3(vert_normals[vert_idx], (*lnor_space)->vec_lnor); } 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. */ @@ -2094,7 +2103,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; @@ -2120,7 +2130,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; } @@ -2132,6 +2142,7 @@ static void split_faces_split_new_verts(Mesh *mesh, { const int verts_len = mesh->totvert - num_new_verts; MVert *mvert = mesh->mvert; + float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(mesh); /* Remember new_verts is a single linklist, so its items are in reversed order... */ MVert *new_mv = &mvert[mesh->totvert - 1]; @@ -2140,9 +2151,10 @@ static void split_faces_split_new_verts(Mesh *mesh, BLI_assert(new_verts->new_index != new_verts->orig_index); CustomData_copy_data(&mesh->vdata, &mesh->vdata, new_verts->orig_index, i, 1); if (new_verts->vnor) { - normal_float_to_short_v3(new_mv->no, new_verts->vnor); + copy_v3_v3(vert_normals[i], new_verts->vnor); } } + BKE_mesh_vertex_normals_clear_dirty(mesh); } /* Perform actual split of edges. */ @@ -2164,12 +2176,6 @@ static void split_faces_split_new_edges(Mesh *mesh, } } -/* Split faces based on the edge angle and loop normals. - * Matches behavior of face splitting in render engines. - * - * NOTE: Will leave CD_NORMAL loop data layer which is - * used by render engines to set shading up. - */ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals) { const int num_polys = mesh->totpoly; @@ -2179,14 +2185,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. */ @@ -2236,6 +2242,7 @@ void BKE_mesh_split_faces(Mesh *mesh, bool free_loop_normals) /* Also frees new_verts/edges temp data, since we used its memarena to allocate them. */ BKE_lnor_spacearr_free(&lnors_spacearr); + BKE_mesh_assert_normals_dirty_or_calculated(mesh); #ifdef VALIDATE_MESH BKE_mesh_validate(mesh, true, true); #endif @@ -2250,10 +2257,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 3086f117707..a4a5fe2be2e 100644 --- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc +++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc @@ -32,9 +32,9 @@ #include "BLI_alloca.h" #include "BLI_array.hh" -#include "BLI_float2.hh" #include "BLI_float4x4.hh" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_mesh_boolean.hh" #include "BLI_mesh_intersect.hh" #include "BLI_span.hh" @@ -807,16 +807,6 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim) #endif // WITH_GMP -/** - * Do a mesh boolean operation directly on meshes (without going back and forth to BMesh). - * \param meshes: An array of Mesh pointers. - * \param obmats: An array of pointers to the obmat matrices that transform local - * coordinates to global ones. It is allowed for the pointers to be null, meaning the - * transformation is the identity. - * \param material_remaps: An array of pointers to arrays of maps from material slot numbers in the - * corresponding mesh to the material slot in the first mesh. It is OK for material_remaps or any - * of its constituent arrays to be empty. - */ Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, Span<const float4x4 *> obmats, const float4x4 &target_transform, diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index 59cdb6a2b27..7d5f156040d 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -32,6 +32,7 @@ #include "DNA_scene_types.h" #include "BLI_edgehash.h" +#include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string.h" @@ -65,6 +66,8 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" +using blender::IndexRange; + /* Define for cases when you want extra validation of mesh * after certain modifications. */ @@ -85,7 +88,6 @@ void BKE_mesh_from_metaball(ListBase *lb, Mesh *me) MVert *mvert; MLoop *mloop, *allloop; MPoly *mpoly; - const float *nors, *verts; int a, *index; dl = (DispList *)lb->first; @@ -104,15 +106,8 @@ void BKE_mesh_from_metaball(ListBase *lb, Mesh *me) me->totvert = dl->nr; me->totpoly = dl->parts; - a = dl->nr; - nors = dl->nors; - verts = dl->verts; - while (a--) { - copy_v3_v3(mvert->co, verts); - normal_float_to_short_v3(mvert->no, nors); - mvert++; - nors += 3; - verts += 3; + for (const int i : IndexRange(dl->nr)) { + copy_v3_v3(me->mvert[i].co, &dl->verts[3 * i]); } a = dl->parts; @@ -139,7 +134,7 @@ void BKE_mesh_from_metaball(ListBase *lb, Mesh *me) BKE_mesh_update_customdata_pointers(me, true); - BKE_mesh_calc_normals(me); + BKE_mesh_normals_tag_dirty(me); BKE_mesh_calc_edges(me, true, false); } @@ -454,10 +449,10 @@ static int mesh_nurbs_displist_to_mdata(const Curve *cu, 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; } } @@ -589,14 +584,14 @@ struct VertLink { static void prependPolyLineVert(ListBase *lb, uint index) { - VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink"); + VertLink *vl = MEM_cnew<VertLink>("VertLink"); vl->index = index; BLI_addhead(lb, vl); } static void appendPolyLineVert(ListBase *lb, uint index) { - VertLink *vl = (VertLink *)MEM_callocN(sizeof(VertLink), "VertLink"); + VertLink *vl = MEM_cnew<VertLink>("VertLink"); vl->index = index; BLI_addtail(lb, vl); } @@ -632,7 +627,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 = (EdgeLink *)MEM_callocN(sizeof(EdgeLink), "EdgeLink"); + EdgeLink *edl = MEM_cnew<EdgeLink>("EdgeLink"); edl->edge = med; BLI_addtail(&edges, edl); @@ -719,7 +714,7 @@ void BKE_mesh_to_curve_nurblist(const Mesh *me, ListBase *nurblist, const int ed VertLink *vl; /* create new 'nurb' within the curve */ - nu = (Nurb *)MEM_callocN(sizeof(Nurb), "MeshNurb"); + nu = MEM_cnew<Nurb>("MeshNurb"); nu->pntsu = totpoly; nu->pntsv = 1; @@ -901,6 +896,20 @@ static Object *object_for_curve_to_mesh_create(const Object *object) return temp_object; } +static void object_for_curve_to_mesh_free(Object *temp_object) +{ + /* Clear edit mode pointers that were explicitly copied to the temporary curve. */ + ID *final_object_data = static_cast<ID *>(temp_object->data); + if (GS(final_object_data->name) == ID_CU) { + Curve &curve = *reinterpret_cast<Curve *>(final_object_data); + curve.editfont = nullptr; + curve.editnurb = nullptr; + } + + BKE_id_free(nullptr, temp_object->data); + BKE_id_free(nullptr, temp_object); +} + /** * Populate `object->runtime.curve_cache` which is then used to create the mesh. */ @@ -917,7 +926,7 @@ static void curve_to_mesh_eval_ensure(Object &object) * will have no modifiers. */ Object bevel_object = {{nullptr}}; if (curve.bevobj != nullptr) { - bevel_object = *curve.bevobj; + memcpy(&bevel_object, curve.bevobj, sizeof(bevel_object)); BLI_listbase_clear(&bevel_object.modifiers); BKE_object_runtime_reset(&bevel_object); curve.bevobj = &bevel_object; @@ -926,7 +935,7 @@ static void curve_to_mesh_eval_ensure(Object &object) /* Same thing for taper. */ Object taper_object = {{nullptr}}; if (curve.taperobj != nullptr) { - taper_object = *curve.taperobj; + memcpy(&taper_object, curve.taperobj, sizeof(taper_object)); BLI_listbase_clear(&taper_object.modifiers); BKE_object_runtime_reset(&taper_object); curve.taperobj = &taper_object; @@ -1003,8 +1012,7 @@ static Mesh *mesh_new_from_curve_type_object(const Object *object) Mesh *mesh = mesh_new_from_evaluated_curve_type_object(temp_object); - BKE_id_free(nullptr, temp_object->data); - BKE_id_free(nullptr, temp_object); + object_for_curve_to_mesh_free(temp_object); return mesh; } @@ -1065,7 +1073,8 @@ static Mesh *mesh_new_from_mesh_object_with_layers(Depsgraph *depsgraph, return nullptr; } - Object object_for_eval = *object; + Object object_for_eval; + memcpy(&object_for_eval, object, sizeof(object_for_eval)); if (object_for_eval.runtime.data_orig != nullptr) { object_for_eval.data = object_for_eval.runtime.data_orig; } @@ -1093,8 +1102,11 @@ static Mesh *mesh_new_from_mesh_object(Depsgraph *depsgraph, 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 != nullptr && mesh_input->edit_mesh->mesh_eval_final) { - mesh_input = mesh_input->edit_mesh->mesh_eval_final; + if (mesh_input->edit_mesh != nullptr) { + Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(object); + if (editmesh_eval_final != nullptr) { + mesh_input = editmesh_eval_final; + } } return mesh_new_from_mesh(object, mesh_input); } @@ -1281,6 +1293,7 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, Scene *scene, Object *ob_eval, ModifierData *md_eval, + const bool use_virtual_modifiers, const bool build_shapekey_layers) { Mesh *me = ob_eval->runtime.data_orig ? (Mesh *)ob_eval->runtime.data_orig : @@ -1303,22 +1316,49 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, BKE_keyblock_convert_to_mesh(kb, me); } - if (mti->type == eModifierTypeType_OnlyDeform) { - int numVerts; - float(*deformedVerts)[3] = BKE_mesh_vert_coords_alloc(me, &numVerts); + Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE); + int numVerts = 0; + float(*deformedVerts)[3] = nullptr; + + if (use_virtual_modifiers) { + VirtualModifierData virtualModifierData; + for (ModifierData *md_eval_virt = + BKE_modifiers_get_virtual_modifierlist(ob_eval, &virtualModifierData); + md_eval_virt && (md_eval_virt != ob_eval->modifiers.first); + md_eval_virt = md_eval_virt->next) { + if (!BKE_modifier_is_enabled(scene, md_eval_virt, eModifierMode_Realtime)) { + continue; + } + /* All virtual modifiers are deform modifiers. */ + const ModifierTypeInfo *mti_virt = BKE_modifier_get_info((ModifierType)md_eval_virt->type); + BLI_assert(mti_virt->type == eModifierTypeType_OnlyDeform); + if (mti_virt->type != eModifierTypeType_OnlyDeform) { + continue; + } + + if (deformedVerts == nullptr) { + deformedVerts = BKE_mesh_vert_coords_alloc(me, &numVerts); + } + mti_virt->deformVerts(md_eval_virt, &mectx, mesh_temp, deformedVerts, numVerts); + } + } - result = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE); + if (mti->type == eModifierTypeType_OnlyDeform) { + if (deformedVerts == nullptr) { + deformedVerts = BKE_mesh_vert_coords_alloc(me, &numVerts); + } + result = mesh_temp; mti->deformVerts(md_eval, &mectx, result, deformedVerts, numVerts); BKE_mesh_vert_coords_apply(result, deformedVerts); if (build_shapekey_layers) { add_shapekey_layers(result, me); } - - MEM_freeN(deformedVerts); } else { - Mesh *mesh_temp = (Mesh *)BKE_id_copy_ex(nullptr, &me->id, nullptr, LIB_ID_COPY_LOCALIZE); + if (deformedVerts != nullptr) { + BKE_mesh_vert_coords_apply(mesh_temp, deformedVerts); + } if (build_shapekey_layers) { add_shapekey_layers(mesh_temp, me); @@ -1332,6 +1372,10 @@ Mesh *BKE_mesh_create_derived_for_modifier(struct Depsgraph *depsgraph, } } + if (deformedVerts != nullptr) { + MEM_freeN(deformedVerts); + } + return result; } @@ -1408,7 +1452,8 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, /* mesh_src might depend on mesh_dst, so we need to do everything with a local copy */ /* TODO(Sybren): the above claim came from 2.7x derived-mesh code (DM_to_mesh); * check whether it is still true with Mesh */ - Mesh tmp = *mesh_dst; + Mesh tmp; + memcpy(&tmp, mesh_dst, sizeof(tmp)); int totvert, totedge /*, totface */ /* UNUSED */, totloop, totpoly; bool did_shapekeys = false; eCDAllocType alloctype = CD_DUPLICATE; @@ -1429,8 +1474,6 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, CustomData_reset(&tmp.ldata); CustomData_reset(&tmp.pdata); - BKE_mesh_ensure_normals(mesh_src); - totvert = tmp.totvert = mesh_src->totvert; totedge = tmp.totedge = mesh_src->totedge; totloop = tmp.totloop = mesh_src->totloop; @@ -1444,6 +1487,18 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, tmp.cd_flag = mesh_src->cd_flag; tmp.runtime.deformed_only = mesh_src->runtime.deformed_only; + tmp.runtime.cd_dirty_poly = mesh_src->runtime.cd_dirty_poly; + tmp.runtime.cd_dirty_vert = mesh_src->runtime.cd_dirty_vert; + + /* Ensure that when no normal layers exist, they are marked dirty, because + * normals might not have been included in the mask of copied layers. */ + if (!CustomData_has_layer(&tmp.vdata, CD_NORMAL)) { + tmp.runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + if (!CustomData_has_layer(&tmp.pdata, CD_NORMAL)) { + tmp.runtime.cd_dirty_poly |= CD_MASK_NORMAL; + } + if (CustomData_has_layer(&mesh_src->vdata, CD_SHAPEKEY)) { KeyBlock *kb; int uid; @@ -1567,6 +1622,8 @@ void BKE_mesh_nomain_to_mesh(Mesh *mesh_src, } BKE_id_free(nullptr, mesh_src); } + + BKE_mesh_assert_normals_dirty_or_calculated(mesh_dst); } void BKE_mesh_nomain_to_meshkey(Mesh *mesh_src, Mesh *mesh_dst, KeyBlock *kb) diff --git a/source/blender/blenkernel/intern/mesh_debug.cc b/source/blender/blenkernel/intern/mesh_debug.cc new file mode 100644 index 00000000000..017f96c2ece --- /dev/null +++ b/source/blender/blenkernel/intern/mesh_debug.cc @@ -0,0 +1,115 @@ +/* + * 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 + * + * Evaluated mesh info printing function, to help track down differences output. + * + * Output from these functions can be evaluated as Python literals. + * See `bmesh_debug.c` for the equivalent #BMesh functionality. + */ + +#ifndef NDEBUG + +# include <stdio.h> + +# include "MEM_guardedalloc.h" + +# include "DNA_mesh_types.h" +# include "DNA_meshdata_types.h" +# include "DNA_object_types.h" + +# include "BLI_utildefines.h" + +# include "BKE_customdata.h" + +# include "BKE_mesh.h" + +# include "BLI_dynstr.h" + +static void mesh_debug_info_from_cd_flag(const Mesh *me, DynStr *dynstr) +{ + BLI_dynstr_append(dynstr, "'cd_flag': {"); + if (me->cd_flag & ME_CDFLAG_VERT_BWEIGHT) { + BLI_dynstr_append(dynstr, "'VERT_BWEIGHT', "); + } + if (me->cd_flag & ME_CDFLAG_EDGE_BWEIGHT) { + BLI_dynstr_append(dynstr, "'EDGE_BWEIGHT', "); + } + if (me->cd_flag & ME_CDFLAG_EDGE_CREASE) { + BLI_dynstr_append(dynstr, "'EDGE_CREASE', "); + } + BLI_dynstr_append(dynstr, "},\n"); +} + +char *BKE_mesh_debug_info(const Mesh *me) +{ + DynStr *dynstr = BLI_dynstr_new(); + char *ret; + + const char *indent4 = " "; + const char *indent8 = " "; + + BLI_dynstr_append(dynstr, "{\n"); + BLI_dynstr_appendf(dynstr, " 'ptr': '%p',\n", (void *)me); + BLI_dynstr_appendf(dynstr, " 'totvert': %d,\n", me->totvert); + BLI_dynstr_appendf(dynstr, " 'totedge': %d,\n", me->totedge); + BLI_dynstr_appendf(dynstr, " 'totface': %d,\n", me->totface); + BLI_dynstr_appendf(dynstr, " 'totpoly': %d,\n", me->totpoly); + + BLI_dynstr_appendf(dynstr, " 'runtime.deformed_only': %d,\n", me->runtime.deformed_only); + BLI_dynstr_appendf(dynstr, " 'runtime.is_original': %d,\n", me->runtime.is_original); + + BLI_dynstr_append(dynstr, " 'vert_layers': (\n"); + CustomData_debug_info_from_layers(&me->vdata, indent8, dynstr); + BLI_dynstr_append(dynstr, " ),\n"); + + BLI_dynstr_append(dynstr, " 'edge_layers': (\n"); + CustomData_debug_info_from_layers(&me->edata, indent8, dynstr); + BLI_dynstr_append(dynstr, " ),\n"); + + BLI_dynstr_append(dynstr, " 'loop_layers': (\n"); + CustomData_debug_info_from_layers(&me->ldata, indent8, dynstr); + BLI_dynstr_append(dynstr, " ),\n"); + + BLI_dynstr_append(dynstr, " 'poly_layers': (\n"); + CustomData_debug_info_from_layers(&me->pdata, indent8, dynstr); + BLI_dynstr_append(dynstr, " ),\n"); + + BLI_dynstr_append(dynstr, " 'tessface_layers': (\n"); + CustomData_debug_info_from_layers(&me->fdata, indent8, dynstr); + BLI_dynstr_append(dynstr, " ),\n"); + + BLI_dynstr_append(dynstr, indent4); + mesh_debug_info_from_cd_flag(me, dynstr); + + BLI_dynstr_append(dynstr, "}\n"); + + ret = BLI_dynstr_get_cstring(dynstr); + BLI_dynstr_free(dynstr); + return ret; +} + +void BKE_mesh_debug_print(const Mesh *me) +{ + char *str = BKE_mesh_debug_info(me); + puts(str); + fflush(stdout); + MEM_freeN(str); +} + +#endif /* NDEBUG */ diff --git a/source/blender/blenkernel/intern/mesh_evaluate.cc b/source/blender/blenkernel/intern/mesh_evaluate.cc index 91fd022a316..5cc1b4e4860 100644 --- a/source/blender/blenkernel/intern/mesh_evaluate.cc +++ b/source/blender/blenkernel/intern/mesh_evaluate.cc @@ -191,7 +191,6 @@ void BKE_mesh_calc_poly_center(const MPoly *mpoly, } } -/* 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) { @@ -249,23 +248,6 @@ float BKE_mesh_calc_poly_uv_area(const MPoly *mpoly, const MLoopUV *uv_array) 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, @@ -445,10 +427,6 @@ bool BKE_mesh_center_median(const Mesh *me, float r_cent[3]) 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; @@ -514,10 +492,6 @@ bool BKE_mesh_center_of_surface(const Mesh *me, float r_cent[3]) 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; @@ -602,12 +576,6 @@ static bool mesh_calc_center_centroid_ex(const MVert *mverts, 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, @@ -800,19 +768,6 @@ void BKE_mesh_convert_mfaces_to_mpolys(Mesh *mesh) 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, @@ -957,12 +912,9 @@ void BKE_mesh_convert_mfaces_to_mpolys_ex(ID *id, #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)) { @@ -999,14 +951,6 @@ void BKE_mesh_mdisp_flip(MDisps *md, const bool use_loop_mdisp_flip) } } -/** - * 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, @@ -1056,11 +1000,6 @@ void BKE_mesh_polygon_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata) 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); @@ -1076,8 +1015,6 @@ void BKE_mesh_polygons_flip(MPoly *mpoly, MLoop *mloop, CustomData *ldata, int t /** \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, @@ -1149,9 +1086,6 @@ void BKE_mesh_flush_hidden_from_polys(Mesh *me) 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, @@ -1248,23 +1182,13 @@ 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, @@ -1318,4 +1242,5 @@ void BKE_mesh_calc_relative_deform(const MPoly *mpoly, 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 7a776b0ecb7..ff2ac8ecee9 100644 --- a/source/blender/blenkernel/intern/mesh_iterators.c +++ b/source/blender/blenkernel/intern/mesh_iterators.c @@ -34,15 +34,11 @@ #include "MEM_guardedalloc.h" -/* Copied from cdDM_foreachMappedVert */ -void BKE_mesh_foreach_mapped_vert(Mesh *mesh, - void (*func)(void *userData, - int index, - const float co[3], - const float no_f[3], - const short no_s[3]), - void *userData, - MeshForeachFlag flag) +void BKE_mesh_foreach_mapped_vert( + Mesh *mesh, + void (*func)(void *userData, int index, const float co[3], const float no[3]), + void *userData, + MeshForeachFlag flag) { if (mesh->edit_mesh != NULL) { BMEditMesh *em = mesh->edit_mesh; @@ -50,7 +46,7 @@ void BKE_mesh_foreach_mapped_vert(Mesh *mesh, BMIter iter; BMVert *eve; int i; - if (mesh->runtime.edit_data->vertexCos != NULL) { + if (mesh->runtime.edit_data != NULL && mesh->runtime.edit_data->vertexCos != NULL) { const float(*vertexCos)[3] = mesh->runtime.edit_data->vertexCos; const float(*vertexNos)[3]; if (flag & MESH_FOREACH_USE_NORMAL) { @@ -62,44 +58,42 @@ void BKE_mesh_foreach_mapped_vert(Mesh *mesh, } BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? vertexNos[i] : NULL; - func(userData, i, vertexCos[i], no, NULL); + func(userData, i, vertexCos[i], no); } } else { BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) { const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? eve->no : NULL; - func(userData, i, eve->co, no, NULL); + func(userData, i, eve->co, no); } } } else { const MVert *mv = mesh->mvert; const int *index = CustomData_get_layer(&mesh->vdata, CD_ORIGINDEX); + const float(*vert_normals)[3] = (flag & MESH_FOREACH_USE_NORMAL) ? + BKE_mesh_vertex_normals_ensure(mesh) : + NULL; if (index) { for (int i = 0; i < mesh->totvert; i++, mv++) { - const short *no = (flag & MESH_FOREACH_USE_NORMAL) ? mv->no : NULL; + const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[i] : NULL; const int orig = *index++; if (orig == ORIGINDEX_NONE) { continue; } - func(userData, orig, mv->co, NULL, no); + func(userData, orig, mv->co, no); } } else { for (int i = 0; i < mesh->totvert; i++, mv++) { - const short *no = (flag & MESH_FOREACH_USE_NORMAL) ? mv->no : NULL; - func(userData, i, mv->co, NULL, no); + const float *no = (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[i] : NULL; + func(userData, i, mv->co, no); } } } } -/** - * 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, @@ -112,7 +106,7 @@ void BKE_mesh_foreach_mapped_edge( BMIter iter; BMEdge *eed; int i; - if (mesh->runtime.edit_data->vertexCos != NULL) { + if (mesh->runtime.edit_data != NULL && mesh->runtime.edit_data->vertexCos != NULL) { const float(*vertexCos)[3] = mesh->runtime.edit_data->vertexCos; BM_mesh_elem_index_ensure(bm, BM_VERT); @@ -151,7 +145,6 @@ void BKE_mesh_foreach_mapped_edge( } } -/* Copied from cdDM_foreachMappedLoop */ void BKE_mesh_foreach_mapped_loop(Mesh *mesh, void (*func)(void *userData, int vertex_index, @@ -171,7 +164,8 @@ void BKE_mesh_foreach_mapped_loop(Mesh *mesh, BMIter iter; BMFace *efa; - const float(*vertexCos)[3] = mesh->runtime.edit_data->vertexCos; + const float(*vertexCos)[3] = mesh->runtime.edit_data ? mesh->runtime.edit_data->vertexCos : + NULL; /* XXX: investigate using EditMesh data. */ const float(*lnors)[3] = (flag & MESH_FOREACH_USE_NORMAL) ? @@ -232,14 +226,13 @@ void BKE_mesh_foreach_mapped_loop(Mesh *mesh, } } -/* Copied from cdDM_foreachMappedFaceCenter */ void BKE_mesh_foreach_mapped_face_center( Mesh *mesh, void (*func)(void *userData, int index, const float cent[3], const float no[3]), void *userData, MeshForeachFlag flag) { - if (mesh->edit_mesh != NULL) { + if (mesh->edit_mesh != NULL && mesh->runtime.edit_data != NULL) { BMEditMesh *em = mesh->edit_mesh; BMesh *bm = em->bm; const float(*polyCos)[3]; @@ -309,7 +302,6 @@ void BKE_mesh_foreach_mapped_face_center( } } -/* Copied from cdDM_foreachMappedFaceCenter */ void BKE_mesh_foreach_mapped_subdiv_face_center( Mesh *mesh, void (*func)(void *userData, int index, const float cent[3], const float no[3]), @@ -319,8 +311,9 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( const MPoly *mp = mesh->mpoly; const MLoop *ml; const MVert *mv; - float _no_buf[3]; - float *no = (flag & MESH_FOREACH_USE_NORMAL) ? _no_buf : NULL; + const float(*vert_normals)[3] = (flag & MESH_FOREACH_USE_NORMAL) ? + BKE_mesh_vertex_normals_ensure(mesh) : + NULL; const int *index = CustomData_get_layer(&mesh->pdata, CD_ORIGINDEX); if (index) { @@ -333,10 +326,11 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( for (int j = 0; j < mp->totloop; j++, ml++) { mv = &mesh->mvert[ml->v]; if (mv->flag & ME_VERT_FACEDOT) { - if (flag & MESH_FOREACH_USE_NORMAL) { - normal_short_to_float_v3(no, mv->no); - } - func(userData, orig, mv->co, no); + + func(userData, + orig, + mv->co, + (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[ml->v] : NULL); } } } @@ -347,10 +341,7 @@ void BKE_mesh_foreach_mapped_subdiv_face_center( for (int j = 0; j < mp->totloop; j++, ml++) { mv = &mesh->mvert[ml->v]; if (mv->flag & ME_VERT_FACEDOT) { - if (flag & MESH_FOREACH_USE_NORMAL) { - normal_short_to_float_v3(no, mv->no); - } - func(userData, i, mv->co, no); + func(userData, i, mv->co, (flag & MESH_FOREACH_USE_NORMAL) ? vert_normals[ml->v] : NULL); } } } @@ -367,8 +358,7 @@ typedef struct MappedVCosData { static void get_vertexcos__mapFunc(void *user_data, int index, const float co[3], - const float UNUSED(no_f[3]), - const short UNUSED(no_s[3])) + const float UNUSED(no[3])) { MappedVCosData *mapped_vcos_data = (MappedVCosData *)user_data; diff --git a/source/blender/blenkernel/intern/mesh_mapping.c b/source/blender/blenkernel/intern/mesh_mapping.c index d28bb9c0744..415cce95d38 100644 --- a/source/blender/blenkernel/intern/mesh_mapping.c +++ b/source/blender/blenkernel/intern/mesh_mapping.c @@ -42,9 +42,6 @@ * \{ */ /* ngon version wip, based on BM_uv_vert_map_create */ -/* this replaces the non bmesh function (in trunk) which takes MTFace's, - * if we ever need it back we could but for now this replaces it because its unused. */ - UvVertMap *BKE_mesh_uv_vert_map_create(const MPoly *mpoly, const MLoop *mloop, const MLoopUV *mloopuv, @@ -250,11 +247,6 @@ static void mesh_vert_poly_or_loop_map_create(MeshElemMap **r_map, *r_mem = indices; } -/** - * Generates a map where the key is the vertex and the value - * is a list of polys that use that vertex as a corner. - * The lists are allocated from one memory pool. - */ void BKE_mesh_vert_poly_map_create(MeshElemMap **r_map, int **r_mem, const MPoly *mpoly, @@ -266,11 +258,6 @@ void BKE_mesh_vert_poly_map_create(MeshElemMap **r_map, mesh_vert_poly_or_loop_map_create(r_map, r_mem, mpoly, mloop, totvert, totpoly, totloop, false); } -/** - * Generates a map where the key is the vertex and the value - * is a list of loops that use that vertex as a corner. - * The lists are allocated from one memory pool. - */ void BKE_mesh_vert_loop_map_create(MeshElemMap **r_map, int **r_mem, const MPoly *mpoly, @@ -282,11 +269,6 @@ void BKE_mesh_vert_loop_map_create(MeshElemMap **r_map, mesh_vert_poly_or_loop_map_create(r_map, r_mem, mpoly, mloop, totvert, totpoly, totloop, true); } -/** - * Generates a map where the key is the edge and the value - * is a list of looptris that use that edge. - * The lists are allocated from one memory pool. - */ void BKE_mesh_vert_looptri_map_create(MeshElemMap **r_map, int **r_mem, const MVert *UNUSED(mvert), @@ -331,11 +313,6 @@ void BKE_mesh_vert_looptri_map_create(MeshElemMap **r_map, *r_mem = indices; } -/** - * Generates a map where the key is the vertex and the value - * is a list of edges that use that vertex as an endpoint. - * The lists are allocated from one memory pool. - */ void BKE_mesh_vert_edge_map_create( MeshElemMap **r_map, int **r_mem, const MEdge *medge, int totvert, int totedge) { @@ -375,10 +352,6 @@ void BKE_mesh_vert_edge_map_create( *r_mem = indices; } -/** - * A version of #BKE_mesh_vert_edge_map_create that references connected vertices directly - * (not their edges). - */ void BKE_mesh_vert_edge_vert_map_create( MeshElemMap **r_map, int **r_mem, const MEdge *medge, int totvert, int totedge) { @@ -418,11 +391,6 @@ void BKE_mesh_vert_edge_vert_map_create( *r_mem = indices; } -/** - * Generates a map where the key is the edge and the value is a list of loops that use that edge. - * Loops indices of a same poly are contiguous and in winding order. - * The lists are allocated from one memory pool. - */ void BKE_mesh_edge_loop_map_create(MeshElemMap **r_map, int **r_mem, const MEdge *UNUSED(medge), @@ -476,11 +444,6 @@ void BKE_mesh_edge_loop_map_create(MeshElemMap **r_map, *r_mem = indices; } -/** - * Generates a map where the key is the edge and the value - * is a list of polygons that use that edge. - * The lists are allocated from one memory pool. - */ void BKE_mesh_edge_poly_map_create(MeshElemMap **r_map, int **r_mem, const MEdge *UNUSED(medge), @@ -529,20 +492,6 @@ void BKE_mesh_edge_poly_map_create(MeshElemMap **r_map, *r_mem = indices; } -/** - * This function creates a map so the source-data (vert/edge/loop/poly) - * can loop over the destination data (using the destination arrays origindex). - * - * This has the advantage that it can operate on any data-types. - * - * \param totsource: The total number of elements that \a final_origindex points to. - * \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 custom-data. - * This would allow an MPoly to loop over its tessfaces. - */ void BKE_mesh_origindex_map_create(MeshElemMap **r_map, int **r_mem, const int totsource, @@ -584,10 +533,6 @@ void BKE_mesh_origindex_map_create(MeshElemMap **r_map, *r_mem = indices; } -/** - * A version of #BKE_mesh_origindex_map_create that takes a looptri array. - * Making a poly -> looptri map. - */ void BKE_mesh_origindex_map_create_looptri(MeshElemMap **r_map, int **r_mem, const MPoly *mpoly, @@ -630,7 +575,7 @@ void BKE_mesh_origindex_map_create_looptri(MeshElemMap **r_map, typedef bool (*MeshRemap_CheckIslandBoundary)(const struct MPoly *mpoly, const struct MLoop *mloop, const struct MEdge *medge, - const int nbr_egde_users, + const int nbr_edge_users, const struct MPoly *mpoly_array, const struct MeshElemMap *edge_poly_map, void *user_data); @@ -833,14 +778,14 @@ static void poly_edge_loop_islands_calc(const MEdge *medge, static bool poly_is_island_boundary_smooth_cb(const MPoly *mp, const MLoop *UNUSED(ml), const MEdge *me, - const int nbr_egde_users, + const int nbr_edge_users, const MPoly *mpoly_array, const MeshElemMap *edge_poly_map, void *UNUSED(user_data)) { /* Edge is sharp if one of its polys is flat, or edge itself is sharp, * or edge is not used by exactly two polygons. */ - if ((mp->flag & ME_SMOOTH) && !(me->flag & ME_SHARP) && (nbr_egde_users == 2)) { + if ((mp->flag & ME_SMOOTH) && !(me->flag & ME_SHARP) && (nbr_edge_users == 2)) { /* In that case, edge appears to be smooth, but we need to check its other poly too. */ const MPoly *mp_other = (mp == &mpoly_array[edge_poly_map->indices[0]]) ? &mpoly_array[edge_poly_map->indices[1]] : @@ -850,14 +795,6 @@ static bool poly_is_island_boundary_smooth_cb(const MPoly *mp, return true; } -/** - * Calculate smooth groups from sharp edges. - * - * \param r_totgroup: The total number of groups, 1 or more. - * \return Polygon aligned array of group index values (bitflags if use_bitflags is true), - * starting at 1 (0 being used as 'invalid' flag). - * Note it's callers's responsibility to MEM_freeN returned array. - */ int *BKE_mesh_calc_smoothgroups(const MEdge *medge, const int totedge, const MPoly *mpoly, @@ -1012,7 +949,7 @@ typedef struct MeshCheckIslandBoundaryUv { static bool mesh_check_island_boundary_uv(const MPoly *UNUSED(mp), const MLoop *ml, const MEdge *me, - const int UNUSED(nbr_egde_users), + const int UNUSED(nbr_edge_users), const MPoly *UNUSED(mpoly_array), const MeshElemMap *UNUSED(edge_poly_map), void *user_data) @@ -1202,10 +1139,6 @@ static bool mesh_calc_islands_loop_poly_uv(MVert *UNUSED(verts), return true; } -/** - * Calculate 'generic' UV islands, i.e. based only on actual geometry data (edge seams), - * not some UV layers coordinates. - */ bool BKE_mesh_calc_islands_loop_poly_edgeseam(MVert *verts, const int totvert, MEdge *edges, @@ -1220,19 +1153,6 @@ bool BKE_mesh_calc_islands_loop_poly_edgeseam(MVert *verts, verts, totvert, edges, totedge, polys, totpoly, loops, totloop, NULL, r_island_store); } -/** - * Calculate UV islands. - * - * \note If no MLoopUV layer is passed, we only consider edges tagged as seams as UV boundaries. - * This has the advantages of simplicity, and being valid/common to all UV maps. - * However, it means actual UV islands without matching UV seams will not be handled correctly... - * If a valid UV layer is passed as \a luvs parameter, - * UV coordinates are also used to detect islands boundaries. - * - * \note All this could be optimized... - * Not sure it would be worth the more complex code, though, - * those loops are supposed to be really quick to do... - */ bool BKE_mesh_calc_islands_loop_poly_uvmap(MVert *verts, const int totvert, MEdge *edges, diff --git a/source/blender/blenkernel/intern/mesh_merge.c b/source/blender/blenkernel/intern/mesh_merge.c index d3d835378ca..134a1344f83 100644 --- a/source/blender/blenkernel/intern/mesh_merge.c +++ b/source/blender/blenkernel/intern/mesh_merge.c @@ -204,38 +204,6 @@ static bool poly_gset_compare_fn(const void *k1, const void *k2) return true; } -/** - * Merge Verts - * - * This frees the given mesh and returns a new mesh. - * - * \param vtargetmap: The table that maps vertices to target vertices. a value of -1 - * indicates a vertex is a target, and is to be kept. - * This array is aligned with 'mesh->totvert' - * \warning \a vtargetmap must **not** contain any chained mapping (v1 -> v2 -> v3 etc.), - * this is not supported and will likely generate corrupted geometry. - * - * \param tot_vtargetmap: The number of non '-1' values in vtargetmap. (not the size) - * - * \param merge_mode: enum with two modes. - * - #MESH_MERGE_VERTS_DUMP_IF_MAPPED - * When called by the Mirror Modifier, - * In this mode it skips any faces that have all vertices merged (to avoid creating pairs - * of faces sharing the same set of vertices) - * - #MESH_MERGE_VERTS_DUMP_IF_EQUAL - * When called by the Array Modifier, - * In this mode, faces where all vertices are merged are double-checked, - * to see whether all target vertices actually make up a poly already. - * Indeed it could be that all of a poly's vertices are merged, - * but merged to vertices that do not make up a single poly, - * in which case the original poly should not be dumped. - * Actually this later behavior could apply to the Mirror Modifier as well, - * but the additional checks are costly and not necessary in the case of mirror, - * because each vertex is only merged to its own mirror. - * - * \note #BKE_mesh_tessface_calc_ex has to run on the returned DM - * if you want to access tessfaces. - */ Mesh *BKE_mesh_merge_verts(Mesh *mesh, const int *vtargetmap, const int tot_vtargetmap, @@ -647,10 +615,18 @@ Mesh *BKE_mesh_merge_verts(Mesh *mesh, } /* Copy over data. #CustomData_add_layer can do this, need to look it up. */ - memcpy(result->mvert, mvert, sizeof(MVert) * STACK_SIZE(mvert)); - memcpy(result->medge, medge, sizeof(MEdge) * STACK_SIZE(medge)); - memcpy(result->mloop, mloop, sizeof(MLoop) * STACK_SIZE(mloop)); - memcpy(result->mpoly, mpoly, sizeof(MPoly) * STACK_SIZE(mpoly)); + if (STACK_SIZE(mvert)) { + memcpy(result->mvert, mvert, sizeof(MVert) * STACK_SIZE(mvert)); + } + if (STACK_SIZE(medge)) { + memcpy(result->medge, medge, sizeof(MEdge) * STACK_SIZE(medge)); + } + if (STACK_SIZE(mloop)) { + memcpy(result->mloop, mloop, sizeof(MLoop) * STACK_SIZE(mloop)); + } + if (STACK_SIZE(mpoly)) { + memcpy(result->mpoly, mpoly, sizeof(MPoly) * STACK_SIZE(mpoly)); + } MEM_freeN(mvert); MEM_freeN(medge); diff --git a/source/blender/blenkernel/intern/mesh_mirror.c b/source/blender/blenkernel/intern/mesh_mirror.c index b20d81e7b9c..abc0b518d92 100644 --- a/source/blender/blenkernel/intern/mesh_mirror.c +++ b/source/blender/blenkernel/intern/mesh_mirror.c @@ -130,14 +130,11 @@ void BKE_mesh_mirror_apply_mirror_on_axis(struct Main *bmain, BM_mesh_free(bm); } -/** - * \warning This should _not_ be used to modify original meshes since - * it doesn't handle shape-keys, use #BKE_mesh_mirror_apply_mirror_on_axis instead. - */ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, Object *ob, const Mesh *mesh, - const int axis) + const int axis, + const bool use_correct_order_on_merge) { const float tolerance_sq = mmd->tolerance * mmd->tolerance; const bool do_vtargetmap = (mmd->flag & MOD_MIR_NO_MERGE) == 0; @@ -239,7 +236,7 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, } /* Copy custom-data to new geometry, - * copy from its self because this data may have been created in the checks above. */ + * copy from itself because this data may have been created in the checks above. */ CustomData_copy_data(&result->vdata, &result->vdata, 0, maxVerts, maxVerts); CustomData_copy_data(&result->edata, &result->edata, 0, maxEdges, maxEdges); /* loops are copied later */ @@ -260,21 +257,51 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, mul_m4_v3(mtx, mv->co); if (do_vtargetmap) { - /* compare location of the original and mirrored vertex, to see if they - * should be mapped for merging */ - if (UNLIKELY(len_squared_v3v3(mv_prev->co, mv->co) < tolerance_sq)) { - *vtmap_a = maxVerts + i; - tot_vtargetmap++; - - /* average location */ - mid_v3_v3v3(mv->co, mv_prev->co, mv->co); - copy_v3_v3(mv_prev->co, mv->co); - } - else { + /* Compare location of the original and mirrored vertex, + * to see if they should be mapped for merging. + * + * Always merge from the copied into the original vertices so it's possible to + * generate a 1:1 mapping by scanning vertices from the beginning of the array + * as is done in #BKE_editmesh_vert_coords_when_deformed. Without this, + * the coordinates returned will sometimes point to the copied vertex locations, see: + * T91444. + * + * However, such a change also affects non-versionable things like some modifiers binding, so + * we cannot enforce that behavior on existing modifiers, in which case we keep using the + * old, incorrect behavior of merging the source vertex into its copy. + */ + if (use_correct_order_on_merge) { + if (UNLIKELY(len_squared_v3v3(mv_prev->co, mv->co) < tolerance_sq)) { + *vtmap_b = i; + tot_vtargetmap++; + + /* average location */ + mid_v3_v3v3(mv->co, mv_prev->co, mv->co); + copy_v3_v3(mv_prev->co, mv->co); + } + else { + *vtmap_b = -1; + } + + /* Fill here to avoid 2x loops. */ *vtmap_a = -1; } + else { + if (UNLIKELY(len_squared_v3v3(mv_prev->co, mv->co) < tolerance_sq)) { + *vtmap_a = maxVerts + i; + tot_vtargetmap++; - *vtmap_b = -1; /* fill here to avoid 2x loops */ + /* average location */ + mid_v3_v3v3(mv->co, mv_prev->co, mv->co); + copy_v3_v3(mv_prev->co, mv->co); + } + else { + *vtmap_a = -1; + } + + /* Fill here to avoid 2x loops. */ + *vtmap_b = -1; + } vtmap_a++; vtmap_b++; @@ -383,7 +410,6 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, CustomData *ldata = &result->ldata; short(*clnors)[2] = CustomData_get_layer(ldata, CD_CUSTOMLOOPNORMAL); MLoopNorSpaceArray lnors_spacearr = {NULL}; - float(*poly_normals)[3] = MEM_mallocN(sizeof(*poly_normals) * totpoly, __func__); /* The transform matrix of a normal must be * the transpose of inverse of transform matrix of the geometry... */ @@ -393,16 +419,8 @@ 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_and_vertex(result->mvert, - result->totvert, - result->mloop, - totloop, - result->mpoly, - totpoly, - poly_normals, - NULL); - BKE_mesh_normals_loop_split(result->mvert, + BKE_mesh_vertex_normals_ensure(mesh), result->totvert, result->medge, result->totedge, @@ -410,7 +428,7 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, loop_normals, totloop, result->mpoly, - poly_normals, + BKE_mesh_poly_normals_ensure(mesh), totpoly, true, mesh->smoothresh, @@ -436,7 +454,6 @@ Mesh *BKE_mesh_mirror_apply_mirror_on_axis_for_modifier(MirrorModifierData *mmd, } } - MEM_freeN(poly_normals); MEM_freeN(loop_normals); BKE_lnor_spacearr_free(&lnors_spacearr); } diff --git a/source/blender/blenkernel/intern/mesh_normals.cc b/source/blender/blenkernel/intern/mesh_normals.cc index 9a761c6fa11..08a17060549 100644 --- a/source/blender/blenkernel/intern/mesh_normals.cc +++ b/source/blender/blenkernel/intern/mesh_normals.cc @@ -38,7 +38,9 @@ #include "BLI_linklist.h" #include "BLI_linklist_stack.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_memarena.h" +#include "BLI_span.hh" #include "BLI_stack.h" #include "BLI_task.h" #include "BLI_utildefines.h" @@ -50,6 +52,8 @@ #include "atomic_ops.h" +using blender::Span; + // #define DEBUG_TIME #ifdef DEBUG_TIME @@ -109,6 +113,52 @@ void BKE_mesh_normals_tag_dirty(Mesh *mesh) mesh->runtime.cd_dirty_poly |= CD_MASK_NORMAL; } +float (*BKE_mesh_vertex_normals_for_write(Mesh *mesh))[3] +{ + CustomData_duplicate_referenced_layer(&mesh->vdata, CD_NORMAL, mesh->totvert); + return (float(*)[3])CustomData_add_layer( + &mesh->vdata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totvert); +} + +float (*BKE_mesh_poly_normals_for_write(Mesh *mesh))[3] +{ + CustomData_duplicate_referenced_layer(&mesh->pdata, CD_NORMAL, mesh->totpoly); + return (float(*)[3])CustomData_add_layer( + &mesh->pdata, CD_NORMAL, CD_CALLOC, nullptr, mesh->totpoly); +} + +void BKE_mesh_vertex_normals_clear_dirty(Mesh *mesh) +{ + mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL; + BKE_mesh_assert_normals_dirty_or_calculated(mesh); +} + +void BKE_mesh_poly_normals_clear_dirty(Mesh *mesh) +{ + mesh->runtime.cd_dirty_poly &= ~CD_MASK_NORMAL; + BKE_mesh_assert_normals_dirty_or_calculated(mesh); +} + +bool BKE_mesh_vertex_normals_are_dirty(const Mesh *mesh) +{ + return mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL; +} + +bool BKE_mesh_poly_normals_are_dirty(const Mesh *mesh) +{ + return mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL; +} + +void BKE_mesh_assert_normals_dirty_or_calculated(const Mesh *mesh) +{ + if (!(mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL)) { + BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL) || mesh->totvert == 0); + } + if (!(mesh->runtime.cd_dirty_poly & CD_MASK_NORMAL)) { + BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL) || mesh->totpoly == 0); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -161,8 +211,6 @@ void BKE_mesh_calc_normals_poly(const MVert *mvert, /* -------------------------------------------------------------------- */ /** \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. * \{ */ @@ -253,18 +301,16 @@ static void mesh_calc_normals_poly_and_vertex_finalize_fn( /* 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_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]) +static void 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]) { TaskParallelSettings settings; BLI_parallel_range_settings_defaults(&settings); @@ -308,22 +354,90 @@ void BKE_mesh_calc_normals_poly_and_vertex(MVert *mvert, /** \name Mesh Normal Calculation * \{ */ -void BKE_mesh_ensure_normals(Mesh *mesh) +const float (*BKE_mesh_vertex_normals_ensure(const Mesh *mesh))[3] { - if (mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) { - BKE_mesh_calc_normals(mesh); + if (!(BKE_mesh_vertex_normals_are_dirty(mesh) || BKE_mesh_poly_normals_are_dirty(mesh))) { + BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL) || mesh->totvert == 0); + return (const float(*)[3])CustomData_get_layer(&mesh->vdata, CD_NORMAL); + } + + if (mesh->totvert == 0) { + return nullptr; + } + + ThreadMutex *normals_mutex = (ThreadMutex *)mesh->runtime.normals_mutex; + BLI_mutex_lock(normals_mutex); + if (!(BKE_mesh_vertex_normals_are_dirty(mesh) || BKE_mesh_poly_normals_are_dirty(mesh))) { + BLI_assert(CustomData_has_layer(&mesh->vdata, CD_NORMAL)); + BLI_mutex_unlock(normals_mutex); + return (const float(*)[3])CustomData_get_layer(&mesh->vdata, CD_NORMAL); } - BLI_assert((mesh->runtime.cd_dirty_vert & CD_MASK_NORMAL) == 0); + + Mesh &mesh_mutable = *const_cast<Mesh *>(mesh); + + float(*vert_normals)[3] = BKE_mesh_vertex_normals_for_write(&mesh_mutable); + float(*poly_normals)[3] = BKE_mesh_poly_normals_for_write(&mesh_mutable); + + mesh_calc_normals_poly_and_vertex(mesh_mutable.mvert, + mesh_mutable.totvert, + mesh_mutable.mloop, + mesh_mutable.totloop, + mesh_mutable.mpoly, + mesh_mutable.totpoly, + poly_normals, + vert_normals); + + BKE_mesh_vertex_normals_clear_dirty(&mesh_mutable); + BKE_mesh_poly_normals_clear_dirty(&mesh_mutable); + + BLI_mutex_unlock(normals_mutex); + return vert_normals; +} + +const float (*BKE_mesh_poly_normals_ensure(const Mesh *mesh))[3] +{ + if (!BKE_mesh_poly_normals_are_dirty(mesh)) { + BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL) || mesh->totpoly == 0); + return (const float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL); + } + + if (mesh->totpoly == 0) { + return nullptr; + } + + ThreadMutex *normals_mutex = (ThreadMutex *)mesh->runtime.normals_mutex; + BLI_mutex_lock(normals_mutex); + if (!BKE_mesh_poly_normals_are_dirty(mesh)) { + BLI_assert(CustomData_has_layer(&mesh->pdata, CD_NORMAL)); + BLI_mutex_unlock(normals_mutex); + return (const float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL); + } + + Mesh &mesh_mutable = *const_cast<Mesh *>(mesh); + + float(*poly_normals)[3] = BKE_mesh_poly_normals_for_write(&mesh_mutable); + + BKE_mesh_calc_normals_poly(mesh_mutable.mvert, + mesh_mutable.totvert, + mesh_mutable.mloop, + mesh_mutable.totloop, + mesh_mutable.mpoly, + mesh_mutable.totpoly, + poly_normals); + + BKE_mesh_poly_normals_clear_dirty(&mesh_mutable); + + BLI_mutex_unlock(normals_mutex); + return poly_normals; } -/** - * Called after calculating all modifiers. - */ void BKE_mesh_ensure_normals_for_display(Mesh *mesh) { switch ((eMeshWrapperType)mesh->runtime.wrapper_type) { + case ME_WRAPPER_TYPE_SUBD: case ME_WRAPPER_TYPE_MDATA: - /* Run code below. */ + BKE_mesh_vertex_normals_ensure(mesh); + BKE_mesh_poly_normals_ensure(mesh); break; case ME_WRAPPER_TYPE_BMESH: { struct BMEditMesh *em = mesh->edit_mesh; @@ -335,70 +449,17 @@ void BKE_mesh_ensure_normals_for_display(Mesh *mesh) return; } } - - 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 == nullptr); - - if (do_vert_normals || do_poly_normals) { - const bool do_add_poly_nors_cddata = (poly_nors == nullptr); - if (do_add_poly_nors_cddata) { - poly_nors = (float(*)[3])MEM_malloc_arrayN( - (size_t)mesh->totpoly, sizeof(*poly_nors), __func__); - } - - /* 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); - } - - mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL; - mesh->runtime.cd_dirty_poly &= ~CD_MASK_NORMAL; - } } -/** - * 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_and_vertex(mesh->mvert, - mesh->totvert, - mesh->mloop, - mesh->totloop, - mesh->mpoly, - mesh->totpoly, - nullptr, - nullptr); + BKE_mesh_vertex_normals_ensure(mesh); #ifdef DEBUG_TIME TIMEIT_END_AVERAGED(BKE_mesh_calc_normals); #endif - mesh->runtime.cd_dirty_vert &= ~CD_MASK_NORMAL; } void BKE_mesh_calc_normals_looptri(MVert *mverts, @@ -445,8 +506,6 @@ void BKE_mesh_calc_normals_looptri(MVert *mverts, if (UNLIKELY(normalize_v3(no) == 0.0f)) { normalize_v3_v3(no, mv->co); } - - normal_float_to_short_v3(mv->no, no); } cleanup: @@ -479,13 +538,6 @@ 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) { @@ -493,10 +545,6 @@ void BKE_lnor_spacearr_tls_init(MLoopNorSpaceArray *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) { @@ -537,11 +585,6 @@ MLoopNorSpace *BKE_lnor_space_create(MLoopNorSpaceArray *lnors_spacearr) /* This threshold is a bit touchy (usual float precision issue), this value seems OK. */ #define LNOR_SPACE_TRIGO_THRESHOLD (1.0f - 1e-4f) -/* Should only be called once. - * Beware, this modifies ref_vec and other_vec in place! - * In case no valid space can be generated, ref_alpha and ref_beta are set to zero - * (which means 'use auto lnors'). - */ void BKE_lnor_space_define(MLoopNorSpace *lnor_space, const float lnor[3], float vec_ref[3], @@ -614,14 +657,6 @@ 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 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, const int ml_index, @@ -783,6 +818,7 @@ struct LoopSplitTaskDataCommon { int (*edge_to_loops)[2]; int *loop_to_poly; const float (*polynors)[3]; + const float (*vert_normals)[3]; int numEdges; int numLoops; @@ -799,7 +835,6 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, const float split_angle, const bool do_sharp_edges_tag) { - const MVert *mverts = data->mverts; const MEdge *medges = data->medges; const MLoop *mloops = data->mloops; @@ -838,7 +873,7 @@ static void mesh_edges_sharp_tag(LoopSplitTaskDataCommon *data, * this way we don't have to compute those later! */ if (loopnors) { - normal_short_to_float_v3(loopnors[ml_curr_index], mverts[ml_curr->v].no); + copy_v3_v3(loopnors[ml_curr_index], data->vert_normals[ml_curr->v]); } /* Check whether current edge might be smooth or sharp */ @@ -901,12 +936,6 @@ 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 auto-smooth! - */ void BKE_edges_sharp_from_angle_set(const struct MVert *mverts, const int UNUSED(numVerts), struct MEdge *medges, @@ -1568,12 +1597,8 @@ static void loop_split_generator(TaskPool *pool, LoopSplitTaskDataCommon *common #endif } -/** - * Compute split normals, i.e. vertex normals associated with each poly (hence 'loop normals'). - * Useful to materialize sharp edges (or non-smooth faces) without actually modifying the geometry - * (splitting edges). - */ void BKE_mesh_normals_loop_split(const MVert *mverts, + const float (*vert_normals)[3], const int UNUSED(numVerts), MEdge *medges, const int numEdges, @@ -1616,7 +1641,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, copy_v3_v3(r_loopnors[ml_index], polynors[mp_index]); } else { - normal_short_to_float_v3(r_loopnors[ml_index], mverts[mloops[ml_index].v].no); + copy_v3_v3(r_loopnors[ml_index], vert_normals[mloops[ml_index].v]); } } } @@ -1674,6 +1699,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, common_data.edge_to_loops = edge_to_loops; common_data.loop_to_poly = loop_to_poly; common_data.polynors = polynors; + common_data.vert_normals = vert_normals; common_data.numEdges = numEdges; common_data.numLoops = numLoops; common_data.numPolys = numPolys; @@ -1725,6 +1751,7 @@ void BKE_mesh_normals_loop_split(const MVert *mverts, * in which case they will be replaced by default loop/vertex normal. */ static void mesh_normals_loop_custom_set(const MVert *mverts, + const float (*vert_normals)[3], const int numVerts, MEdge *medges, const int numEdges, @@ -1756,6 +1783,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, /* Compute current lnor spacearr. */ BKE_mesh_normals_loop_split(mverts, + vert_normals, numVerts, medges, numEdges, @@ -1775,7 +1803,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, if (use_vertices) { for (int i = 0; i < numVerts; i++) { if (is_zero_v3(r_custom_loopnors[i])) { - normal_short_to_float_v3(r_custom_loopnors[i], mverts[i].no); + copy_v3_v3(r_custom_loopnors[i], vert_normals[i]); } } } @@ -1878,6 +1906,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, /* And now, recompute our new auto lnors and lnor spacearr! */ BKE_lnor_spacearr_clear(&lnors_spacearr); BKE_mesh_normals_loop_split(mverts, + vert_normals, numVerts, medges, numEdges, @@ -1959,6 +1988,7 @@ static void mesh_normals_loop_custom_set(const MVert *mverts, } void BKE_mesh_normals_loop_custom_set(const MVert *mverts, + const float (*vert_normals)[3], const int numVerts, MEdge *medges, const int numEdges, @@ -1971,6 +2001,7 @@ void BKE_mesh_normals_loop_custom_set(const MVert *mverts, short (*r_clnors_data)[2]) { mesh_normals_loop_custom_set(mverts, + vert_normals, numVerts, medges, numEdges, @@ -1985,6 +2016,7 @@ void BKE_mesh_normals_loop_custom_set(const MVert *mverts, } void BKE_mesh_normals_loop_custom_from_vertices_set(const MVert *mverts, + const float (*vert_normals)[3], float (*r_custom_vertnors)[3], const int numVerts, MEdge *medges, @@ -1997,6 +2029,7 @@ void BKE_mesh_normals_loop_custom_from_vertices_set(const MVert *mverts, short (*r_clnors_data)[2]) { mesh_normals_loop_custom_set(mverts, + vert_normals, numVerts, medges, numEdges, @@ -2024,22 +2057,8 @@ static void mesh_set_custom_normals(Mesh *mesh, float (*r_custom_nors)[3], const &mesh->ldata, CD_CUSTOMLOOPNORMAL, CD_CALLOC, nullptr, numloops); } - float(*polynors)[3] = (float(*)[3])CustomData_get_layer(&mesh->pdata, CD_NORMAL); - bool free_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; - } - mesh_normals_loop_custom_set(mesh->mvert, + BKE_mesh_vertex_normals_ensure(mesh), mesh->totvert, mesh->medge, mesh->totedge, @@ -2047,46 +2066,22 @@ static void mesh_set_custom_normals(Mesh *mesh, float (*r_custom_nors)[3], const r_custom_nors, mesh->totloop, mesh->mpoly, - polynors, + BKE_mesh_poly_normals_ensure(mesh), mesh->totpoly, clnors, use_vertices); - - if (free_polynors) { - MEM_freeN(polynors); - } } -/** - * Higher level functions hiding most of the code needed around call to - * #BKE_mesh_normals_loop_custom_set(). - * - * \param r_custom_loopnors: is not const, since code will replace zero_v3 normals there - * with automatically computed vectors. - */ void BKE_mesh_set_custom_normals(Mesh *mesh, float (*r_custom_loopnors)[3]) { mesh_set_custom_normals(mesh, r_custom_loopnors, false); } -/** - * Higher level functions hiding most of the code needed around call to - * #BKE_mesh_normals_loop_custom_from_vertices_set(). - * - * \param r_custom_vertnors: is not const, since code will replace zero_v3 normals there - * with automatically computed vectors. - */ void BKE_mesh_set_custom_normals_from_vertices(Mesh *mesh, float (*r_custom_vertnors)[3]) { mesh_set_custom_normals(mesh, r_custom_vertnors, true); } -/** - * Computes average per-vertex normals from given custom loop normals. - * - * \param clnors: The computed custom loop normals. - * \param r_vert_clnors: The (already allocated) array where to store averaged per-vertex normals. - */ void BKE_mesh_normals_loop_to_vertex(const int numVerts, const MLoop *mloops, const int numLoops, diff --git a/source/blender/blenkernel/intern/mesh_remap.c b/source/blender/blenkernel/intern/mesh_remap.c index 53a31cbbc7a..a9f61e9827b 100644 --- a/source/blender/blenkernel/intern/mesh_remap.c +++ b/source/blender/blenkernel/intern/mesh_remap.c @@ -50,7 +50,7 @@ static CLG_LogRef LOG = {"bke.mesh"}; /* -------------------------------------------------------------------- */ -/** \name Some generic helpers. +/** \name Some Generic Helpers * \{ */ static bool mesh_remap_bvhtree_query_nearest(BVHTreeFromMesh *treedata, @@ -117,22 +117,12 @@ static bool mesh_remap_bvhtree_query_raycast(BVHTreeFromMesh *treedata, /** \} */ -/** - * \name Auto-match. +/* -------------------------------------------------------------------- */ +/** \name Auto-match. * * Find transform of a mesh to get best match with another. * \{ */ -/** - * Compute a value of the difference between both given meshes. - * The smaller the result, the better the match. - * - * We return the inverse of the average of the inversed - * shortest distance from each dst vertex to src ones. - * In other words, beyond a certain (relatively small) distance, all differences have more or less - * the same weight in final result, which allows to reduce influence of a few high differences, - * in favor of a global good matching. - */ float BKE_mesh_remap_calc_difference_from_mesh(const SpaceTransform *space_transform, const MVert *verts_dst, const int numverts_dst, @@ -268,9 +258,6 @@ static void mesh_calc_eigen_matrix(const MVert *verts, copy_v3_v3(r_mat[3], center); } -/** - * Set r_space_transform so that best bbox of dst matches best bbox of src. - */ void BKE_mesh_remap_find_best_match_from_mesh(const MVert *verts_dst, const int numverts_dst, Mesh *me_src, @@ -328,7 +315,7 @@ void BKE_mesh_remap_find_best_match_from_mesh(const MVert *verts_dst, /** \} */ /* -------------------------------------------------------------------- */ -/** \name Mesh to mesh mapping +/** \name Mesh to Mesh Mapping * \{ */ void BKE_mesh_remap_calc_source_cddata_masks_from_map_modes(const int UNUSED(vert_mode), @@ -607,6 +594,7 @@ void BKE_mesh_remap_calc_verts_from_mesh(const int mode, MPoly *polys_src = me_src->mpoly; MLoop *loops_src = me_src->mloop; float(*vcos_src)[3] = BKE_mesh_vert_coords_alloc(me_src, NULL); + const float(*vert_normals_src)[3] = BKE_mesh_vertex_normals_ensure(me_src); size_t tmp_buff_size = MREMAP_DEFAULT_BUFSIZE; float(*vcos)[3] = MEM_mallocN(sizeof(*vcos) * tmp_buff_size, __func__); @@ -618,7 +606,7 @@ void BKE_mesh_remap_calc_verts_from_mesh(const int mode, if (mode == MREMAP_MODE_VERT_POLYINTERP_VNORPROJ) { for (i = 0; i < numverts_dst; i++) { copy_v3_v3(tmp_co, verts_dst[i].co); - normal_short_to_float_v3(tmp_no, verts_dst[i].no); + copy_v3_v3(tmp_no, vert_normals_src[i]); /* Convert the vertex to tree coordinates, if needed. */ if (space_transform) { @@ -964,6 +952,8 @@ void BKE_mesh_remap_calc_edges_from_mesh(const int mode, BKE_bvhtree_from_mesh_get(&treedata, me_src, BVHTREE_FROM_EDGES, 2); + const float(*vert_normals_dst)[3] = BKE_mesh_vertex_normals_ensure(me_src); + for (i = 0; i < numedges_dst; i++) { /* For each dst edge, we sample some rays from it (interpolated from its vertices) * and use their hits to interpolate from source edges. */ @@ -983,8 +973,8 @@ void BKE_mesh_remap_calc_edges_from_mesh(const int mode, copy_v3_v3(v1_co, verts_dst[me->v1].co); copy_v3_v3(v2_co, verts_dst[me->v2].co); - normal_short_to_float_v3(v1_no, verts_dst[me->v1].no); - normal_short_to_float_v3(v2_no, verts_dst[me->v2].no); + copy_v3_v3(v1_no, vert_normals_dst[me->v1]); + copy_v3_v3(v2_no, vert_normals_dst[me->v2]); /* We do our transform here, allows to interpolate from normals already in src space. */ if (space_transform) { @@ -1255,6 +1245,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, const SpaceTransform *space_transform, const float max_dist, const float ray_radius, + Mesh *mesh_dst, MVert *verts_dst, const int numverts_dst, MEdge *edges_dst, @@ -1264,7 +1255,6 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, MPoly *polys_dst, const int numpolys_dst, CustomData *ldata_dst, - CustomData *pdata_dst, const bool use_split_nors_dst, const float split_angle_dst, const bool dirty_nors_dst, @@ -1310,9 +1300,9 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, 1) : 0); - float(*poly_nors_src)[3] = NULL; - float(*loop_nors_src)[3] = NULL; - float(*poly_nors_dst)[3] = NULL; + const float(*poly_nors_src)[3] = NULL; + const float(*loop_nors_src)[3] = NULL; + const float(*poly_nors_dst)[3] = NULL; float(*loop_nors_dst)[3] = NULL; float(*poly_cents_src)[3] = NULL; @@ -1369,23 +1359,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, const bool need_pnors_dst = need_lnors_dst || need_pnors_src; if (need_pnors_dst) { - /* Cache poly nors into a temp CDLayer. */ - poly_nors_dst = CustomData_get_layer(pdata_dst, CD_NORMAL); - const bool do_poly_nors_dst = (poly_nors_dst == NULL); - if (!poly_nors_dst) { - poly_nors_dst = CustomData_add_layer( - pdata_dst, CD_NORMAL, CD_CALLOC, NULL, numpolys_dst); - CustomData_set_layer_flag(pdata_dst, CD_NORMAL, CD_FLAG_TEMPORARY); - } - if (dirty_nors_dst || do_poly_nors_dst) { - BKE_mesh_calc_normals_poly(verts_dst, - numverts_dst, - loops_dst, - numloops_dst, - polys_dst, - numpolys_dst, - poly_nors_dst); - } + poly_nors_dst = BKE_mesh_poly_normals_ensure(mesh_dst); } if (need_lnors_dst) { short(*custom_nors_dst)[2] = CustomData_get_layer(ldata_dst, CD_CUSTOMLOOPNORMAL); @@ -1400,6 +1374,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, } if (dirty_nors_dst || do_loop_nors_dst) { BKE_mesh_normals_loop_split(verts_dst, + BKE_mesh_vertex_normals_ensure(mesh_dst), numverts_dst, edges_dst, numedges_dst, @@ -1407,7 +1382,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, loop_nors_dst, numloops_dst, polys_dst, - (const float(*)[3])poly_nors_dst, + poly_nors_dst, numpolys_dst, use_split_nors_dst, split_angle_dst, @@ -1418,8 +1393,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, } if (need_pnors_src || need_lnors_src) { if (need_pnors_src) { - poly_nors_src = CustomData_get_layer(&me_src->pdata, CD_NORMAL); - BLI_assert(poly_nors_src != NULL); + poly_nors_src = BKE_mesh_poly_normals_ensure(me_src); } if (need_lnors_src) { loop_nors_src = CustomData_get_layer(&me_src->ldata, CD_NORMAL); @@ -1661,7 +1635,7 @@ void BKE_mesh_remap_calc_loops_from_mesh(const int mode, if (mesh_remap_bvhtree_query_nearest( tdata, &nearest, tmp_co, max_dist_sq, &hit_dist)) { float(*nor_dst)[3]; - float(*nors_src)[3]; + const float(*nors_src)[3]; float best_nor_dot = -2.0f; float best_sqdist_fallback = FLT_MAX; int best_index_src = -1; @@ -2201,41 +2175,24 @@ void BKE_mesh_remap_calc_polys_from_mesh(const int mode, const SpaceTransform *space_transform, const float max_dist, const float ray_radius, + Mesh *mesh_dst, MVert *verts_dst, - const int numverts_dst, MLoop *loops_dst, - const int numloops_dst, MPoly *polys_dst, const int numpolys_dst, - CustomData *pdata_dst, - const bool dirty_nors_dst, Mesh *me_src, MeshPairRemap *r_map) { const float full_weight = 1.0f; const float max_dist_sq = max_dist * max_dist; - float(*poly_nors_dst)[3] = NULL; + const float(*poly_nors_dst)[3] = NULL; float tmp_co[3], tmp_no[3]; int i; BLI_assert(mode & MREMAP_MODE_POLY); if (mode & (MREMAP_USE_NORMAL | MREMAP_USE_NORPROJ)) { - /* Cache poly nors into a temp CDLayer. */ - poly_nors_dst = CustomData_get_layer(pdata_dst, CD_NORMAL); - if (!poly_nors_dst) { - poly_nors_dst = CustomData_add_layer(pdata_dst, CD_NORMAL, CD_CALLOC, NULL, numpolys_dst); - CustomData_set_layer_flag(pdata_dst, CD_NORMAL, CD_FLAG_TEMPORARY); - } - if (dirty_nors_dst) { - BKE_mesh_calc_normals_poly(verts_dst, - numverts_dst, - loops_dst, - numloops_dst, - polys_dst, - numpolys_dst, - poly_nors_dst); - } + poly_nors_dst = BKE_mesh_poly_normals_ensure(mesh_dst); } BKE_mesh_remap_init(r_map, numpolys_dst); diff --git a/source/blender/blenkernel/intern/mesh_remesh_voxel.cc b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc index 9f5703a015d..50464da86e9 100644 --- a/source/blender/blenkernel/intern/mesh_remesh_voxel.cc +++ b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc @@ -31,8 +31,8 @@ #include "MEM_guardedalloc.h" #include "BLI_array.hh" -#include "BLI_float3.hh" #include "BLI_index_range.hh" +#include "BLI_math_vec_types.hh" #include "BLI_span.hh" #include "DNA_mesh_types.h" @@ -410,7 +410,8 @@ struct Mesh *BKE_mesh_remesh_voxel_fix_poles(const Mesh *mesh) { const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); - const BMeshCreateParams bmesh_create_params = {true}; + BMeshCreateParams bmesh_create_params{}; + bmesh_create_params.use_toolflags = true; BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params); BMeshFromMeshParams bmesh_from_mesh_params{}; diff --git a/source/blender/blenkernel/intern/mesh_runtime.c b/source/blender/blenkernel/intern/mesh_runtime.c index 7ac4c29f0ee..e7e5064df7c 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.c +++ b/source/blender/blenkernel/intern/mesh_runtime.c @@ -45,18 +45,53 @@ * \{ */ /** - * Default values defined at read time. + * \brief Initialize the runtime mutexes of the given mesh. + * + * Any existing mutexes will be overridden. */ -void BKE_mesh_runtime_reset(Mesh *mesh) +static void mesh_runtime_init_mutexes(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.normals_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime normals_mutex"); + BLI_mutex_init(mesh->runtime.normals_mutex); + mesh->runtime.render_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime render_mutex"); + BLI_mutex_init(mesh->runtime.render_mutex); +} + +/** + * \brief free the mutexes of the given mesh runtime. + */ +static void mesh_runtime_free_mutexes(Mesh *mesh) +{ + if (mesh->runtime.eval_mutex != NULL) { + BLI_mutex_end(mesh->runtime.eval_mutex); + MEM_freeN(mesh->runtime.eval_mutex); + mesh->runtime.eval_mutex = NULL; + } + if (mesh->runtime.normals_mutex != NULL) { + BLI_mutex_end(mesh->runtime.normals_mutex); + MEM_freeN(mesh->runtime.normals_mutex); + mesh->runtime.normals_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; + } +} + +void BKE_mesh_runtime_init_data(Mesh *mesh) +{ + mesh_runtime_init_mutexes(mesh); +} + +void BKE_mesh_runtime_free_data(Mesh *mesh) +{ + BKE_mesh_runtime_clear_cache(mesh); + mesh_runtime_free_mutexes(mesh); } -/* Clear all pointers which we don't want to be shared on copying the datablock. - * However, keep all the flags which defines what the mesh is (for example, that - * it's deformed only, or that its custom data layers are out of date.) */ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag)) { Mesh_Runtime *runtime = &mesh->runtime; @@ -69,17 +104,11 @@ void BKE_mesh_runtime_reset_on_copy(Mesh *mesh, const int UNUSED(flag)) runtime->bvh_cache = NULL; runtime->shrinkwrap_data = NULL; - mesh->runtime.eval_mutex = MEM_mallocN(sizeof(ThreadMutex), "mesh runtime eval_mutex"); - BLI_mutex_init(mesh->runtime.eval_mutex); + mesh_runtime_init_mutexes(mesh); } void BKE_mesh_runtime_clear_cache(Mesh *mesh) { - if (mesh->runtime.eval_mutex != NULL) { - BLI_mutex_end(mesh->runtime.eval_mutex); - MEM_freeN(mesh->runtime.eval_mutex); - mesh->runtime.eval_mutex = NULL; - } if (mesh->runtime.mesh_eval != NULL) { mesh->runtime.mesh_eval->edit_mesh = NULL; BKE_id_free(NULL, mesh->runtime.mesh_eval); @@ -90,7 +119,6 @@ void BKE_mesh_runtime_clear_cache(Mesh *mesh) BKE_mesh_runtime_clear_edit_data(mesh); } -/* This is a ported copy of DM_ensure_looptri_data(dm) */ /** * Ensure the array is large enough * @@ -99,6 +127,7 @@ void BKE_mesh_runtime_clear_cache(Mesh *mesh) */ static void mesh_ensure_looptri_data(Mesh *mesh) { + /* This is a ported copy of `DM_ensure_looptri_data(dm)`. */ const uint totpoly = mesh->totpoly; const int looptris_len = poly_to_tri_count(totpoly, mesh->totloop); @@ -124,7 +153,6 @@ static void mesh_ensure_looptri_data(Mesh *mesh) } } -/* This is a ported copy of CDDM_recalc_looptri(dm). */ void BKE_mesh_runtime_looptri_recalc(Mesh *mesh) { mesh_ensure_looptri_data(mesh); @@ -144,9 +172,9 @@ void BKE_mesh_runtime_looptri_recalc(Mesh *mesh) mesh->runtime.looptris.array_wip = NULL; } -/* This is a ported copy of dm_getNumLoopTri(dm). */ int BKE_mesh_runtime_looptri_len(const Mesh *mesh) { + /* This is a ported copy of `dm_getNumLoopTri(dm)`. */ const int looptri_len = poly_to_tri_count(mesh->totpoly, mesh->totloop); BLI_assert(ELEM(mesh->runtime.looptris.len, 0, looptri_len)); return looptri_len; @@ -158,11 +186,6 @@ static void mesh_runtime_looptri_recalc_isolated(void *userdata) BKE_mesh_runtime_looptri_recalc(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; @@ -184,7 +207,6 @@ const MLoopTri *BKE_mesh_runtime_looptri_ensure(const Mesh *mesh) return looptri; } -/* This is a copy of DM_verttri_from_looptri(). */ void BKE_mesh_runtime_verttri_from_looptri(MVertTri *r_verttri, const MLoop *mloop, const MLoopTri *looptri, @@ -276,130 +298,10 @@ void BKE_mesh_batch_cache_free(Mesh *me) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Mesh runtime debug helpers. +/** \name Mesh Runtime Validation * \{ */ -/* evaluated mesh info printing function, - * to help track down differences output */ #ifndef NDEBUG -# include "BLI_dynstr.h" - -static void mesh_runtime_debug_info_layers(DynStr *dynstr, CustomData *cd) -{ - int type; - - for (type = 0; type < CD_NUMTYPES; type++) { - if (CustomData_has_layer(cd, type)) { - /* 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); - const int pt_size = pt ? (int)(MEM_allocN_len(pt) / size) : 0; - const char *structname; - int structnum; - CustomData_file_write_info(type, &structname, &structnum); - BLI_dynstr_appendf( - dynstr, - " dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n", - name, - structname, - type, - (const void *)pt, - size, - pt_size); - } - } -} - -char *BKE_mesh_runtime_debug_info(Mesh *me_eval) -{ - DynStr *dynstr = BLI_dynstr_new(); - char *ret; - - BLI_dynstr_append(dynstr, "{\n"); - BLI_dynstr_appendf(dynstr, " 'ptr': '%p',\n", (void *)me_eval); -# if 0 - const char *tstr; - switch (me_eval->type) { - case DM_TYPE_CDDM: - tstr = "DM_TYPE_CDDM"; - break; - case DM_TYPE_CCGDM: - tstr = "DM_TYPE_CCGDM"; - break; - default: - tstr = "UNKNOWN"; - break; - } - BLI_dynstr_appendf(dynstr, " 'type': '%s',\n", tstr); -# endif - BLI_dynstr_appendf(dynstr, " 'totvert': %d,\n", me_eval->totvert); - BLI_dynstr_appendf(dynstr, " 'totedge': %d,\n", me_eval->totedge); - BLI_dynstr_appendf(dynstr, " 'totface': %d,\n", me_eval->totface); - BLI_dynstr_appendf(dynstr, " 'totpoly': %d,\n", me_eval->totpoly); - BLI_dynstr_appendf(dynstr, " 'deformed_only': %d,\n", me_eval->runtime.deformed_only); - - BLI_dynstr_append(dynstr, " 'vertexLayers': (\n"); - mesh_runtime_debug_info_layers(dynstr, &me_eval->vdata); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'edgeLayers': (\n"); - mesh_runtime_debug_info_layers(dynstr, &me_eval->edata); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'loopLayers': (\n"); - mesh_runtime_debug_info_layers(dynstr, &me_eval->ldata); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'polyLayers': (\n"); - mesh_runtime_debug_info_layers(dynstr, &me_eval->pdata); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, " 'tessFaceLayers': (\n"); - mesh_runtime_debug_info_layers(dynstr, &me_eval->fdata); - BLI_dynstr_append(dynstr, " ),\n"); - - BLI_dynstr_append(dynstr, "}\n"); - - ret = BLI_dynstr_get_cstring(dynstr); - BLI_dynstr_free(dynstr); - return ret; -} - -void BKE_mesh_runtime_debug_print(Mesh *me_eval) -{ - char *str = BKE_mesh_runtime_debug_info(me_eval); - puts(str); - fflush(stdout); - MEM_freeN(str); -} - -/* XXX Should go in customdata file? */ -void BKE_mesh_runtime_debug_print_cdlayers(CustomData *data) -{ - int i; - const CustomDataLayer *layer; - - printf("{\n"); - - for (i = 0, layer = data->layers; i < data->totlayer; i++, layer++) { - - const char *name = CustomData_layertype_name(layer->type); - const int size = CustomData_sizeof(layer->type); - const char *structname; - int structnum; - CustomData_file_write_info(layer->type, &structname, &structnum); - printf(" dict(name='%s', struct='%s', type=%d, ptr='%p', elem=%d, length=%d),\n", - name, - structname, - layer->type, - (const void *)layer->data, - size, - (int)(MEM_allocN_len(layer->data) / size)); - } - - printf("}\n"); -} bool BKE_mesh_runtime_is_valid(Mesh *me_eval) { diff --git a/source/blender/blenkernel/intern/mesh_sample.cc b/source/blender/blenkernel/intern/mesh_sample.cc index 5388f6e530e..a046cc68bf2 100644 --- a/source/blender/blenkernel/intern/mesh_sample.cc +++ b/source/blender/blenkernel/intern/mesh_sample.cc @@ -29,12 +29,13 @@ 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{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]; @@ -56,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()); @@ -67,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>()); }); } @@ -76,12 +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) { 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]; @@ -103,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()); @@ -114,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>()); }); } @@ -122,12 +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) { 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; @@ -138,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()); } @@ -162,15 +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()); 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]; @@ -190,15 +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()); 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]; @@ -215,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(); @@ -242,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: { @@ -266,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 e5e971fd574..73cef6b925b 100644 --- a/source/blender/blenkernel/intern/mesh_tangent.c +++ b/source/blender/blenkernel/intern/mesh_tangent.c @@ -115,12 +115,6 @@ static void set_tspace(const SMikkTSpaceContext *pContext, p_res[3] = face_sign; } -/** - * 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! - */ void BKE_mesh_calc_loop_tangent_single_ex(const MVert *mverts, const int UNUSED(numVerts), const MLoop *mloops, @@ -172,12 +166,6 @@ void BKE_mesh_calc_loop_tangent_single_ex(const MVert *mverts, } } -/** - * Wrapper around BKE_mesh_calc_loop_tangent_single_ex, which takes care of most boiling code. - * \note - * - There must be a valid loop's CD_NORMALS available. - * - The mesh should be made of only tris and quads! - */ void BKE_mesh_calc_loop_tangent_single(Mesh *mesh, const char *uvmap, float (*r_looptangents)[4], @@ -236,7 +224,8 @@ typedef struct { MLoopUV *mloopuv; /* texture coordinates */ const MPoly *mpoly; /* indices */ const MLoop *mloop; /* indices */ - const MVert *mvert; /* vertices & normals */ + const MVert *mvert; /* vertex coordinates */ + const float (*vert_normals)[3]; const float (*orco)[3]; float (*tangent)[4]; /* destination */ int numTessFaces; @@ -410,8 +399,7 @@ finally: } } else { - const short *no = pMesh->mvert[pMesh->mloop[loop_index].v].no; - normal_short_to_float_v3(r_no, no); + copy_v3_v3(r_no, pMesh->vert_normals[pMesh->mloop[loop_index].v]); } } @@ -485,12 +473,6 @@ void BKE_mesh_add_loop_tangent_named_layer_for_uv(CustomData *uv_data, } } -/** - * Here we get some useful information such as active uv layer name and - * search if it is already in tangent_names. - * Also, we calculate tangent_mask that works as a descriptor of tangents state. - * If tangent_mask has changed, then recalculate tangents. - */ void BKE_mesh_calc_loop_tangent_step_0(const CustomData *loopData, bool calc_active_tangent, const char (*tangent_names)[MAX_NAME], @@ -564,9 +546,6 @@ void BKE_mesh_calc_loop_tangent_step_0(const CustomData *loopData, } } -/** - * See: #BKE_editmesh_loop_tangent_calc (matching logic). - */ void BKE_mesh_calc_loop_tangent_ex(const MVert *mvert, const MPoly *mpoly, const uint mpoly_len, @@ -578,6 +557,7 @@ void BKE_mesh_calc_loop_tangent_ex(const MVert *mvert, bool calc_active_tangent, const char (*tangent_names)[MAX_NAME], int tangent_names_len, + const float (*vert_normals)[3], const float (*poly_normals)[3], const float (*loop_normals)[3], const float (*vert_orco)[3], @@ -672,6 +652,7 @@ void BKE_mesh_calc_loop_tangent_ex(const MVert *mvert, mesh2tangent->num_face_as_quad_map = num_face_as_quad_map; #endif mesh2tangent->mvert = mvert; + mesh2tangent->vert_normals = vert_normals; mesh2tangent->mpoly = mpoly; mesh2tangent->mloop = mloop; mesh2tangent->looptri = looptri; @@ -764,7 +745,8 @@ void BKE_mesh_calc_loop_tangents(Mesh *me_eval, calc_active_tangent, tangent_names, tangent_names_len, - CustomData_get_layer(&me_eval->pdata, CD_NORMAL), + BKE_mesh_vertex_normals_ensure(me_eval), + BKE_mesh_poly_normals_ensure(me_eval), CustomData_get_layer(&me_eval->ldata, CD_NORMAL), CustomData_get_layer(&me_eval->vdata, CD_ORCO), /* may be NULL */ /* result */ diff --git a/source/blender/blenkernel/intern/mesh_tessellate.c b/source/blender/blenkernel/intern/mesh_tessellate.c index 213f2929d63..241aefc418c 100644 --- a/source/blender/blenkernel/intern/mesh_tessellate.c +++ b/source/blender/blenkernel/intern/mesh_tessellate.c @@ -154,17 +154,6 @@ static void mesh_loops_to_tessdata(CustomData *fdata, } } -/** - * Recreate #MFace Tessellation. - * - * \param do_face_nor_copy: Controls whether the normals from the poly - * are copied to the tessellated faces. - * - * \return number of tessellation faces. - * - * \note This doesn't use multi-threading like #BKE_mesh_recalc_looptri since - * it's not used in many places and #MFace should be phased out. - */ int BKE_mesh_tessface_calc_ex(CustomData *fdata, CustomData *ldata, CustomData *pdata, @@ -712,9 +701,6 @@ static void mesh_recalc_looptri__multi_threaded(const MLoop *mloop, &settings); } -/** - * Calculate tessellation into #MLoopTri which exist only for this purpose. - */ void BKE_mesh_recalc_looptri(const MLoop *mloop, const MPoly *mpoly, const MVert *mvert, @@ -730,14 +716,6 @@ void BKE_mesh_recalc_looptri(const MLoop *mloop, } } -/** - * A version of #BKE_mesh_recalc_looptri which takes pre-calculated polygon normals - * (used to avoid having to calculate the face normal for NGON tessellation). - * - * \note Only use this function if normals have already been calculated, there is no need - * to calculate normals just to use this function as it will cause the normals for triangles - * to be calculated which aren't needed for tessellation. - */ void BKE_mesh_recalc_looptri_with_normals(const MLoop *mloop, const MPoly *mpoly, const MVert *mvert, diff --git a/source/blender/blenkernel/intern/mesh_validate.c b/source/blender/blenkernel/intern/mesh_validate.c index 08668d55cf4..005c916b4e0 100644 --- a/source/blender/blenkernel/intern/mesh_validate.c +++ b/source/blender/blenkernel/intern/mesh_validate.c @@ -193,6 +193,7 @@ static int search_polyloop_cmp(const void *v1, const void *v2) /* Else, sort on loopstart. */ return sp1->loopstart > sp2->loopstart ? 1 : sp1->loopstart < sp2->loopstart ? -1 : 0; } + /** \} */ /* -------------------------------------------------------------------- */ @@ -213,21 +214,6 @@ static int search_polyloop_cmp(const void *v1, const void *v2) } \ } while (0) -/** - * Validate the mesh, \a do_fixes requires \a mesh to be non-null. - * - * \return false if no changes needed to be made. - * - * Vertex Normals - * ============== - * - * While zeroed normals are checked, these checks aren't comprehensive. - * Technically, to detect errors here a normal recalculation and comparison is necessary. - * However this function is mainly to prevent severe errors in geometry - * (invalid data that will crash Blender, or cause some features to behave incorrectly), - * not to detect subtle differences in the resulting normals which could be caused - * by importers that load normals (for example). - */ /* NOLINTNEXTLINE: readability-function-size */ bool BKE_mesh_validate_arrays(Mesh *mesh, MVert *mverts, @@ -317,6 +303,12 @@ bool BKE_mesh_validate_arrays(Mesh *mesh, recalc_flag.edges = do_fixes; } + const float(*vert_normals)[3] = NULL; + BKE_mesh_assert_normals_dirty_or_calculated(mesh); + if (!BKE_mesh_vertex_normals_are_dirty(mesh)) { + vert_normals = BKE_mesh_vertex_normals_ensure(mesh); + } + for (i = 0; i < totvert; i++, mv++) { bool fix_normal = true; @@ -331,13 +323,13 @@ bool BKE_mesh_validate_arrays(Mesh *mesh, } } - if (mv->no[j] != 0) { + if (vert_normals && vert_normals[i][j] != 0.0f) { fix_normal = false; break; } } - if (fix_normal) { + if (vert_normals && fix_normal) { /* If the vertex normal accumulates to zero or isn't part of a face, the location is used. * When the location is also zero, a zero normal warning should not be raised. * since this is the expected behavior of normal calculation. @@ -350,7 +342,8 @@ bool BKE_mesh_validate_arrays(Mesh *mesh, if (!is_zero_v3(mv->co)) { PRINT_ERR("\tVertex %u: has zero normal, assuming Z-up normal", i); if (do_fixes) { - mv->no[2] = SHRT_MAX; + float *normal = (float *)vert_normals[i]; + normal[2] = 1.0f; fix_flag.verts = true; } } @@ -997,9 +990,6 @@ static bool mesh_validate_customdata(CustomData *data, return is_valid; } -/** - * \returns is_valid. - */ bool BKE_mesh_validate_all_customdata(CustomData *vdata, const uint totvert, CustomData *edata, @@ -1018,6 +1008,10 @@ bool BKE_mesh_validate_all_customdata(CustomData *vdata, CustomData_MeshMasks mask = {0}; if (check_meshmask) { mask = CD_MASK_MESH; + /* Normal data isn't in the mask since it is derived data, + * but it is valid and should not be removed. */ + mask.vmask |= CD_MASK_NORMAL; + mask.pmask |= CD_MASK_NORMAL; } is_valid &= mesh_validate_customdata( @@ -1061,11 +1055,6 @@ bool BKE_mesh_validate_all_customdata(CustomData *vdata, return is_valid; } -/** - * Validates and corrects a Mesh. - * - * \returns true if a change is made. - */ bool BKE_mesh_validate(Mesh *me, const bool do_verbose, const bool cddata_check_mask) { bool is_valid = true; @@ -1112,13 +1101,6 @@ bool BKE_mesh_validate(Mesh *me, const bool do_verbose, const bool cddata_check_ return false; } -/** - * Checks if a Mesh is valid without any modification. This is always verbose. - * - * \see #DM_is_valid to call on derived meshes - * - * \returns is_valid. - */ bool BKE_mesh_is_valid(Mesh *me) { const bool do_verbose = true; @@ -1127,6 +1109,8 @@ bool BKE_mesh_is_valid(Mesh *me) bool is_valid = true; bool changed = true; + BKE_mesh_assert_normals_dirty_or_calculated(me); + is_valid &= BKE_mesh_validate_all_customdata( &me->vdata, me->totvert, @@ -1162,10 +1146,6 @@ bool BKE_mesh_is_valid(Mesh *me) return is_valid; } -/** - * Check all material indices of polygons are valid, invalid ones are set to 0. - * \returns is_valid. - */ bool BKE_mesh_validate_material_indices(Mesh *me) { /* Cast to unsigned to catch negative indices too. */ @@ -1196,9 +1176,9 @@ bool BKE_mesh_validate_material_indices(Mesh *me) /** \name Mesh Stripping (removing invalid data) * \{ */ -/* We need to keep this for edge creation (for now?), and some old readfile code... */ void BKE_mesh_strip_loose_faces(Mesh *me) { + /* NOTE: We need to keep this for edge creation (for now?), and some old `readfile.c` code. */ MFace *f; int a, b; @@ -1217,13 +1197,6 @@ void BKE_mesh_strip_loose_faces(Mesh *me) } } -/** - * Works on both loops and polys! - * - * \note It won't try to guess which loops of an invalid poly to remove! - * this is the work of the caller, to mark those loops... - * See e.g. #BKE_mesh_validate_arrays(). - */ void BKE_mesh_strip_loose_polysloops(Mesh *me) { MPoly *p; @@ -1329,6 +1302,7 @@ void BKE_mesh_strip_loose_edges(Mesh *me) MEM_freeN(new_idx); } + /** \} */ /* -------------------------------------------------------------------- */ @@ -1512,10 +1486,6 @@ static void mesh_calc_edges_mdata(MVert *UNUSED(allvert), *r_totedge = totedge_final; } -/** - * If the mesh is from a very old blender version, - * convert mface->edcode to edge drawflags - */ void BKE_mesh_calc_edges_legacy(Mesh *me, const bool use_old) { MEdge *medge; @@ -1565,12 +1535,6 @@ void BKE_mesh_calc_edges_loose(Mesh *mesh) } } -/** - * Calculate/create edges from tessface data - * - * \param mesh: The mesh to add edges into - */ - void BKE_mesh_calc_edges_tessface(Mesh *mesh) { const int numFaces = mesh->totface; diff --git a/source/blender/blenkernel/intern/mesh_validate.cc b/source/blender/blenkernel/intern/mesh_validate.cc index 574ab785445..c8fae3cf880 100644 --- a/source/blender/blenkernel/intern/mesh_validate.cc +++ b/source/blender/blenkernel/intern/mesh_validate.cc @@ -220,9 +220,6 @@ static void clear_hash_tables(MutableSpan<EdgeMap> edge_maps) } // namespace blender::bke::calc_edges -/** - * Calculate edges from polygons. - */ void BKE_mesh_calc_edges(Mesh *mesh, bool keep_existing_edges, const bool select_new_edges) { using namespace blender; diff --git a/source/blender/blenkernel/intern/mesh_wrapper.c b/source/blender/blenkernel/intern/mesh_wrapper.c index bc1ffeb8cf4..d1f15cf9007 100644 --- a/source/blender/blenkernel/intern/mesh_wrapper.c +++ b/source/blender/blenkernel/intern/mesh_wrapper.c @@ -36,6 +36,7 @@ #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" #include "DNA_object_types.h" #include "BLI_ghash.h" @@ -50,8 +51,14 @@ #include "BKE_mesh.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_subdiv.h" +#include "BKE_subdiv_mesh.h" +#include "BKE_subdiv_modifier.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" Mesh *BKE_mesh_wrapper_from_editmesh_with_coords(BMEditMesh *em, const CustomData_MeshMasks *cd_mask_extra, @@ -106,7 +113,8 @@ static void mesh_wrapper_ensure_mdata_isolated(void *userdata) me->runtime.wrapper_type = ME_WRAPPER_TYPE_MDATA; switch (geom_type_orig) { - case ME_WRAPPER_TYPE_MDATA: { + case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: { break; /* Quiet warning. */ } case ME_WRAPPER_TYPE_BMESH: { @@ -157,6 +165,7 @@ bool BKE_mesh_wrapper_minmax(const Mesh *me, float min[3], float max[3]) case ME_WRAPPER_TYPE_BMESH: return BKE_editmesh_cache_calc_minmax(me->edit_mesh, me->runtime.edit_data, min, max); case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: return BKE_mesh_minmax(me, min, max); } BLI_assert_unreachable(); @@ -191,7 +200,8 @@ void BKE_mesh_wrapper_vert_coords_copy(const Mesh *me, } return; } - case ME_WRAPPER_TYPE_MDATA: { + case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: { BLI_assert(vert_coords_len <= me->totvert); const MVert *mvert = me->mvert; for (int i = 0; i < vert_coords_len; i++) { @@ -228,7 +238,8 @@ void BKE_mesh_wrapper_vert_coords_copy_with_mat4(const Mesh *me, } return; } - case ME_WRAPPER_TYPE_MDATA: { + case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: { BLI_assert(vert_coords_len == me->totvert); const MVert *mvert = me->mvert; for (int i = 0; i < vert_coords_len; i++) { @@ -252,6 +263,7 @@ int BKE_mesh_wrapper_vert_len(const Mesh *me) case ME_WRAPPER_TYPE_BMESH: return me->edit_mesh->bm->totvert; case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: return me->totvert; } BLI_assert_unreachable(); @@ -264,6 +276,7 @@ int BKE_mesh_wrapper_edge_len(const Mesh *me) case ME_WRAPPER_TYPE_BMESH: return me->edit_mesh->bm->totedge; case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: return me->totedge; } BLI_assert_unreachable(); @@ -276,6 +289,7 @@ int BKE_mesh_wrapper_loop_len(const Mesh *me) case ME_WRAPPER_TYPE_BMESH: return me->edit_mesh->bm->totloop; case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: return me->totloop; } BLI_assert_unreachable(); @@ -288,6 +302,7 @@ int BKE_mesh_wrapper_poly_len(const Mesh *me) case ME_WRAPPER_TYPE_BMESH: return me->edit_mesh->bm->totface; case ME_WRAPPER_TYPE_MDATA: + case ME_WRAPPER_TYPE_SUBD: return me->totpoly; } BLI_assert_unreachable(); @@ -295,3 +310,73 @@ int BKE_mesh_wrapper_poly_len(const Mesh *me) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name CPU Subdivision Evaluation + * \{ */ + +Mesh *BKE_mesh_wrapper_ensure_subdivision(const Object *ob, Mesh *me) +{ + ThreadMutex *mesh_eval_mutex = (ThreadMutex *)me->runtime.eval_mutex; + BLI_mutex_lock(mesh_eval_mutex); + + if (me->runtime.wrapper_type == ME_WRAPPER_TYPE_SUBD) { + BLI_mutex_unlock(mesh_eval_mutex); + return me->runtime.mesh_eval; + } + + SubsurfModifierData *smd = BKE_object_get_last_subsurf_modifier(ob); + if (!smd) { + BLI_mutex_unlock(mesh_eval_mutex); + return me; + } + + /* Initialize the settings before ensuring the descriptor as this is checked to decide whether + * subdivision is needed at all, and checking the descriptor status might involve checking if the + * data is out-of-date, which is a very expensive operation. */ + SubdivToMeshSettings mesh_settings; + mesh_settings.resolution = me->runtime.subsurf_resolution; + mesh_settings.use_optimal_display = me->runtime.subsurf_use_optimal_display; + + if (mesh_settings.resolution < 3) { + BLI_mutex_unlock(mesh_eval_mutex); + return me; + } + + const bool apply_render = me->runtime.subsurf_apply_render; + + SubdivSettings subdiv_settings; + BKE_subsurf_modifier_subdiv_settings_init(&subdiv_settings, smd, apply_render); + if (subdiv_settings.level == 0) { + BLI_mutex_unlock(mesh_eval_mutex); + return me; + } + + SubsurfRuntimeData *runtime_data = BKE_subsurf_modifier_ensure_runtime(smd); + + Subdiv *subdiv = BKE_subsurf_modifier_subdiv_descriptor_ensure(smd, &subdiv_settings, me, false); + if (subdiv == NULL) { + /* Happens on bad topology, but also on empty input mesh. */ + BLI_mutex_unlock(mesh_eval_mutex); + return me; + } + + Mesh *subdiv_mesh = BKE_subdiv_to_mesh(subdiv, &mesh_settings, me); + + if (subdiv != runtime_data->subdiv) { + BKE_subdiv_free(subdiv); + } + + if (subdiv_mesh != me) { + if (me->runtime.mesh_eval != NULL) { + BKE_id_free(NULL, me->runtime.mesh_eval); + } + me->runtime.mesh_eval = subdiv_mesh; + me->runtime.wrapper_type = ME_WRAPPER_TYPE_SUBD; + } + + BLI_mutex_unlock(mesh_eval_mutex); + return me->runtime.mesh_eval; +} + +/** \} */ diff --git a/source/blender/blenkernel/intern/modifier.c b/source/blender/blenkernel/intern/modifier.c index 6f6cf12f023..e1fd8ff45d1 100644 --- a/source/blender/blenkernel/intern/modifier.c +++ b/source/blender/blenkernel/intern/modifier.c @@ -133,9 +133,6 @@ const ModifierTypeInfo *BKE_modifier_get_info(ModifierType type) return NULL; } -/** - * Get the idname of the modifier type's panel, which was defined in the #panelRegister callback. - */ void BKE_modifier_type_panel_id(ModifierType type, char *r_idname) { const ModifierTypeInfo *mti = BKE_modifier_get_info(type); @@ -213,9 +210,6 @@ void BKE_modifier_free(ModifierData *md) BKE_modifier_free_ex(md, 0); } -/** - * Use instead of `BLI_remlink` when the object's active modifier should change. - */ void BKE_modifier_remove_from_list(Object *ob, ModifierData *md) { BLI_assert(BLI_findindex(&ob->modifiers, md) != -1); @@ -328,9 +322,6 @@ void BKE_modifiers_foreach_tex_link(Object *ob, TexWalkFunc walk, void *userData } } -/* callback's can use this - * to avoid copying every member. - */ void BKE_modifier_copydata_generic(const ModifierData *md_src, ModifierData *md_dst, const int UNUSED(flag)) @@ -457,13 +448,6 @@ void BKE_modifier_set_error(const Object *ob, ModifierData *md, const char *_for CLOG_ERROR(&LOG, "Object: \"%s\", Modifier: \"%s\", %s", ob->id.name + 2, md->name, md->error); } -/* used for buttons, to find out if the 'draw deformed in editmode' option is - * there - * - * also used in transform_conversion.c, to detect CrazySpace [tm] (2nd arg - * then is NULL) - * also used for some mesh tools to give warnings - */ int BKE_modifiers_get_cage_index(const Scene *scene, Object *ob, int *r_lastPossibleCageIndex, @@ -547,12 +531,6 @@ bool BKE_modifiers_is_particle_enabled(Object *ob) return (md && md->mode & (eModifierMode_Realtime | eModifierMode_Render)); } -/** - * Check whether is enabled. - * - * \param scene: Current scene, may be NULL, - * in which case isDisabled callback of the modifier is never called. - */ bool BKE_modifier_is_enabled(const struct Scene *scene, ModifierData *md, int required_mode) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); @@ -575,12 +553,6 @@ bool BKE_modifier_is_enabled(const struct Scene *scene, ModifierData *md, int re return true; } -/** - * Check whether given modifier is not local (i.e. from linked data) when the object is a library - * override. - * - * \param md: May be NULL, in which case we consider it as a non-local modifier case. - */ bool BKE_modifier_is_nonlocal_in_liboverride(const Object *ob, const ModifierData *md) { return (ID_IS_OVERRIDE_LIBRARY(ob) && @@ -674,8 +646,6 @@ ModifierData *BKE_modifier_get_last_preview(const struct Scene *scene, return tmp_md; } -/* This is to include things that are not modifiers in the evaluation of the modifier stack, for - * example parenting to an armature. */ ModifierData *BKE_modifiers_get_virtual_modifierlist(const Object *ob, VirtualModifierData *virtualModifierData) { @@ -719,9 +689,6 @@ ModifierData *BKE_modifiers_get_virtual_modifierlist(const Object *ob, return md; } -/* Takes an object and returns its first selected armature, else just its armature - * This should work for multiple armatures per object - */ Object *BKE_modifiers_is_deformed_by_armature(Object *ob) { if (ob->type == OB_GPENCIL) { @@ -790,9 +757,6 @@ Object *BKE_modifiers_is_deformed_by_meshdeform(Object *ob) return NULL; } -/* Takes an object and returns its first selected lattice, else just its lattice - * This should work for multiple lattices per object - */ Object *BKE_modifiers_is_deformed_by_lattice(Object *ob) { VirtualModifierData virtualModifierData; @@ -816,9 +780,6 @@ Object *BKE_modifiers_is_deformed_by_lattice(Object *ob) return NULL; } -/* Takes an object and returns its first selected curve, else just its curve - * This should work for multiple curves per object - */ Object *BKE_modifiers_is_deformed_by_curve(Object *ob) { VirtualModifierData virtualModifierData; @@ -946,7 +907,6 @@ void BKE_modifier_free_temporary_data(ModifierData *md) } } -/* ensure modifier correctness when changing ob->data */ void BKE_modifiers_test_object(Object *ob) { ModifierData *md; @@ -967,45 +927,29 @@ void BKE_modifiers_test_object(Object *ob) } } -/* where should this go?, it doesn't fit well anywhere :S - campbell */ - -/* elubie: changed this to default to the same dir as the render output - * to prevent saving to C:\ on Windows */ - -/* campbell: logic behind this... - * - * - if the ID is from a library, return library path - * - else if the file has been saved return the blend file path. - * - else if the file isn't saved and the ID isn't from a library, return the temp dir. - */ const char *BKE_modifier_path_relbase(Main *bmain, Object *ob) { - if (G.relbase_valid || ID_IS_LINKED(ob)) { + /* - If the ID is from a library, return library path. + * - Else if the file has been saved return the blend file path. + * - Else if the file isn't saved and the ID isn't from a library, return the temp dir. + */ + if ((bmain->filepath[0] != '\0') || ID_IS_LINKED(ob)) { return ID_BLEND_PATH(bmain, &ob->id); } - /* last resort, better than using "" which resolves to the current - * working directory */ + /* Last resort, better than using "" which resolves to the current working directory. */ return BKE_tempdir_session(); } const char *BKE_modifier_path_relbase_from_global(Object *ob) { - if (G.relbase_valid || ID_IS_LINKED(ob)) { - return ID_BLEND_PATH_FROM_GLOBAL(&ob->id); - } - - /* last resort, better than using "" which resolves to the current - * working directory */ - return BKE_tempdir_session(); + return BKE_modifier_path_relbase(G_MAIN, ob); } -/* initializes the path with either */ void BKE_modifier_path_init(char *path, int path_maxlen, const char *name) { - /* elubie: changed this to default to the same dir as the render output - * to prevent saving to C:\ on Windows */ - BLI_join_dirfile(path, path_maxlen, G.relbase_valid ? "//" : BKE_tempdir_session(), name); + const char *blendfile_path = BKE_main_blendfile_path_from_global(); + BLI_join_dirfile(path, path_maxlen, blendfile_path[0] ? "//" : BKE_tempdir_session(), name); } /** @@ -1026,6 +970,7 @@ static void modwrap_dependsOnNormals(Mesh *me) } break; } + case ME_WRAPPER_TYPE_SUBD: case ME_WRAPPER_TYPE_MDATA: BKE_mesh_calc_normals(me); break; @@ -1039,7 +984,6 @@ struct Mesh *BKE_modifier_modify_mesh(ModifierData *md, struct Mesh *me) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - BLI_assert(CustomData_has_layer(&me->pdata, CD_NORMAL) == false); if (me->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH) { if ((mti->flags & eModifierTypeFlag_AcceptsBMesh) == 0) { @@ -1060,8 +1004,6 @@ void BKE_modifier_deform_verts(ModifierData *md, int numVerts) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - BLI_assert(!me || CustomData_has_layer(&me->pdata, CD_NORMAL) == false); - if (me && mti->dependsOnNormals && mti->dependsOnNormals(md)) { modwrap_dependsOnNormals(me); } @@ -1076,8 +1018,6 @@ void BKE_modifier_deform_vertsEM(ModifierData *md, int numVerts) { const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - BLI_assert(!me || CustomData_has_layer(&me->pdata, CD_NORMAL) == false); - if (me && mti->dependsOnNormals && mti->dependsOnNormals(md)) { BKE_mesh_calc_normals(me); } @@ -1086,15 +1026,6 @@ void BKE_modifier_deform_vertsEM(ModifierData *md, /* end modifier callback wrappers */ -/** - * Get evaluated mesh for other evaluated object, which is used as an operand for the modifier, - * e.g. second operand for boolean modifier. - * Note that modifiers in stack always get fully evaluated COW ID pointers, - * never original ones. Makes things simpler. - * - * \param get_cage_mesh: Return evaluated mesh with only deforming modifiers applied - * (i.e. mesh topology remains the same as original one, a.k.a. 'cage' mesh). - */ Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval, const bool get_cage_mesh) { @@ -1105,8 +1036,11 @@ Mesh *BKE_modifier_get_evaluated_mesh_from_evaluated_object(Object *ob_eval, BMEditMesh *em = BKE_editmesh_from_object(ob_eval); /* 'em' might not exist yet in some cases, just after loading a .blend file, see T57878. */ if (em != NULL) { - me = (get_cage_mesh && em->mesh_eval_cage != NULL) ? em->mesh_eval_cage : - em->mesh_eval_final; + Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(ob_eval); + Mesh *editmesh_eval_cage = BKE_object_get_editmesh_eval_cage(ob_eval); + + me = (get_cage_mesh && editmesh_eval_cage != NULL) ? editmesh_eval_cage : + editmesh_eval_final; } } if (me == NULL) { diff --git a/source/blender/blenkernel/intern/movieclip.c b/source/blender/blenkernel/intern/movieclip.c index 0c2ac841b87..88da789cdc4 100644 --- a/source/blender/blenkernel/intern/movieclip.c +++ b/source/blender/blenkernel/intern/movieclip.c @@ -60,6 +60,7 @@ #include "BLT_translation.h" #include "BKE_anim_data.h" +#include "BKE_bpath.h" #include "BKE_colortools.h" #include "BKE_global.h" #include "BKE_idtype.h" @@ -69,6 +70,7 @@ #include "BKE_main.h" #include "BKE_movieclip.h" #include "BKE_node.h" +#include "BKE_node_tree_update.h" #include "BKE_tracking.h" #include "IMB_imbuf.h" @@ -104,7 +106,7 @@ static void movie_clip_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_s MovieClip *movie_clip_dst = (MovieClip *)id_dst; const MovieClip *movie_clip_src = (const MovieClip *)id_src; - /* We never handle usercount here for own data. */ + /* We never handle user-count here for own data. */ const int flag_subdata = flag | LIB_ID_CREATE_NO_USER_REFCOUNT; movie_clip_dst->anim = NULL; @@ -132,19 +134,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); } } @@ -165,6 +167,12 @@ static void movie_clip_foreach_cache(ID *id, function_callback(id, &key, (void **)&movie_clip->tracking.camera.intrinsics, 0, user_data); } +static void movie_clip_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + MovieClip *movie_clip = (MovieClip *)id; + BKE_bpath_foreach_path_fixed_process(bpath_data, movie_clip->filepath); +} + static void write_movieTracks(BlendWriter *writer, ListBase *tracks) { MovieTrackingTrack *track; @@ -347,6 +355,7 @@ IDTypeInfo IDType_ID_MC = { .name_plural = "movieclips", .translation_context = BLT_I18NCONTEXT_ID_MOVIECLIP, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = movie_clip_init_data, .copy_data = movie_clip_copy_data, @@ -354,6 +363,7 @@ IDTypeInfo IDType_ID_MC = { .make_local = NULL, .foreach_id = movie_clip_foreach_id, .foreach_cache = movie_clip_foreach_cache, + .foreach_path = movie_clip_foreach_path, .owner_get = NULL, .blend_write = movieclip_blend_write, @@ -535,10 +545,6 @@ static void movieclip_convert_multilayer_add_pass(void *UNUSED(layer), #endif /* WITH_OPENEXR */ -/* Will try to make image buffer usable when originating from the multi-layer - * source. - * Internally finds a first combined pass and uses that as a buffer. Not ideal, - * but is better than a complete empty buffer. */ void BKE_movieclip_convert_multilayer_ibuf(struct ImBuf *ibuf) { if (ibuf == NULL) { @@ -842,7 +848,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; @@ -947,7 +953,7 @@ static MovieClip *movieclip_alloc(Main *bmain, const char *name) static void movieclip_load_get_size(MovieClip *clip) { int width, height; - MovieClipUser user = {0}; + MovieClipUser user = *DNA_struct_default_get(MovieClipUser); user.framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, 1); BKE_movieclip_get_size(clip, &user, &width, &height); @@ -979,10 +985,6 @@ static void detect_clip_source(Main *bmain, MovieClip *clip) } } -/* checks if image was already loaded, then returns same image - * otherwise creates new. - * does not load ibuf itself - * pass on optional frame for #name images */ MovieClip *BKE_movieclip_file_add(Main *bmain, const char *name) { MovieClip *clip; @@ -1176,7 +1178,7 @@ static ImBuf *get_postprocessed_cached_frame(const MovieClip *clip, return NULL; } - /* postprocessing happened for other frame */ + /* Postprocessing happened for other frame. */ if (cache->postprocessed.framenr != framenr) { return NULL; } @@ -1612,7 +1614,6 @@ void BKE_movieclip_get_aspect(MovieClip *clip, float *aspx, float *aspy) *aspy = clip->aspy / clip->aspx / clip->tracking.camera.pixel_aspect; } -/* get segments of cached frames. useful for debugging cache policies */ void BKE_movieclip_get_cache_segments(MovieClip *clip, MovieClipUser *user, int *r_totseg, @@ -1695,17 +1696,7 @@ void BKE_movieclip_reload(Main *bmain, MovieClip *clip) movieclip_calc_length(clip); - /* same as for image update -- don't use notifiers because they are not 100% sure to succeeded - * (node trees which are not currently visible wouldn't be refreshed) - */ - { - Scene *scene; - for (scene = bmain->scenes.first; scene; scene = scene->id.next) { - if (scene->nodetree) { - nodeUpdateID(scene->nodetree, &clip->id); - } - } - } + BKE_ntree_update_tag_id_changed(bmain, &clip->id); } void BKE_movieclip_update_scopes(MovieClip *clip, MovieClipUser *user, MovieClipScopes *scopes) @@ -1848,9 +1839,6 @@ static void movieclip_build_proxy_ibuf( IMB_freeImBuf(scaleibuf); } -/* 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, int clip_flag, struct MovieDistortion *distortion, @@ -1892,9 +1880,6 @@ void BKE_movieclip_build_proxy_frame(MovieClip *clip, } } -/* 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, ImBuf *ibuf, struct MovieDistortion *distortion, @@ -1925,6 +1910,11 @@ void BKE_movieclip_build_proxy_frame_for_ibuf(MovieClip *clip, } } +bool BKE_movieclip_proxy_enabled(MovieClip *clip) +{ + return clip->flag & MCLIP_USE_PROXY; +} + float BKE_movieclip_remap_scene_to_clip_frame(const MovieClip *clip, float framenr) { return framenr - (float)clip->start_frame + 1.0f; @@ -2145,4 +2135,5 @@ void BKE_movieclip_free_gputexture(struct MovieClip *clip) MEM_freeN(tex); } } + /** \} */ diff --git a/source/blender/blenkernel/intern/multires.c b/source/blender/blenkernel/intern/multires.c index eaa11a6683a..fbad7d98630 100644 --- a/source/blender/blenkernel/intern/multires.c +++ b/source/blender/blenkernel/intern/multires.c @@ -330,9 +330,6 @@ MultiresModifierData *find_multires_modifier_before(Scene *scene, ModifierData * return NULL; } -/* used for applying scale on mdisps layer and syncing subdivide levels when joining objects - * use_first - return first multires modifier if all multires'es are disabled - */ MultiresModifierData *get_multires_modifier(Scene *scene, Object *ob, bool use_first) { ModifierData *md; @@ -519,7 +516,6 @@ static int get_levels_from_disps(Object *ob) return totlvl; } -/* reset the multires levels to match the number of mdisps */ void multiresModifier_set_levels_from_disps(MultiresModifierData *mmd, Object *ob) { Mesh *me = ob->data; @@ -712,7 +708,6 @@ static void multires_del_higher(MultiresModifierData *mmd, Object *ob, int lvl) multires_set_tot_level(ob, mmd, lvl); } -/* (direction = 1) for delete higher, (direction = 0) for lower (not implemented yet) */ void multiresModifier_del_levels(MultiresModifierData *mmd, Scene *scene, Object *ob, @@ -1295,7 +1290,6 @@ DerivedMesh *multires_make_derived_from_derived( return result; } -/* Adapted from sculptmode.c */ void old_mdisps_bilinear(float out[3], float (*disps)[3], const int st, float u, float v) { int x, y, x2, y2; @@ -1349,8 +1343,6 @@ void old_mdisps_bilinear(float out[3], float (*disps)[3], const int st, float u, add_v3_v3v3(out, d2[0], d2[1]); } -/* If 'ob_src' and 'ob_dst' both have multires modifiers, synchronize them - * such that 'ob_dst' has the same total number of levels as 'ob_src'. */ void multiresModifier_sync_levels_ex(Object *ob_dst, MultiresModifierData *mmd_src, MultiresModifierData *mmd_dst) @@ -1469,7 +1461,6 @@ void multiresModifier_prepare_join(struct Depsgraph *depsgraph, multires_apply_smat(depsgraph, scene, ob, mat); } -/* update multires data after topology changing */ void multires_topology_changed(Mesh *me) { MDisps *mdisp = NULL, *cur = NULL; @@ -1496,7 +1487,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; @@ -1504,11 +1495,6 @@ void multires_topology_changed(Mesh *me) } } -/* Makes sure data from an external file is fully read. - * - * Since the multires data files only contain displacement vectors without knowledge about - * subdivision level some extra work is needed. Namely make is to all displacement grids have - * proper level and number of displacement vectors set. */ void multires_ensure_external_read(struct Mesh *mesh, int top_level) { if (!CustomData_external_test(&mesh->ldata, CD_MDISPS)) { @@ -1544,7 +1530,6 @@ void multiresModifier_ensure_external_read(struct Mesh *mesh, const MultiresModi /***************** Multires interpolation stuff *****************/ -/* Find per-corner coordinate with given per-face UV coord */ int mdisp_rot_face_to_crn(struct MVert *UNUSED(mvert), struct MPoly *mpoly, struct MLoop *UNUSED(mloop), diff --git a/source/blender/blenkernel/intern/multires_reshape.c b/source/blender/blenkernel/intern/multires_reshape.c index bd52d70b223..e0bb3cf792c 100644 --- a/source/blender/blenkernel/intern/multires_reshape.c +++ b/source/blender/blenkernel/intern/multires_reshape.c @@ -69,10 +69,6 @@ bool multiresModifier_reshapeFromVertcos(struct Depsgraph *depsgraph, return true; } -/* Returns truth on success, false otherwise. - * - * This function might fail in cases like source and destination not having - * matched amount of vertices. */ bool multiresModifier_reshapeFromObject(struct Depsgraph *depsgraph, struct MultiresModifierData *mmd, struct Object *dst, diff --git a/source/blender/blenkernel/intern/multires_reshape.h b/source/blender/blenkernel/intern/multires_reshape.h index 36ecf1a6395..a038ce5f108 100644 --- a/source/blender/blenkernel/intern/multires_reshape.h +++ b/source/blender/blenkernel/intern/multires_reshape.h @@ -106,6 +106,9 @@ typedef struct MultiresReshapeContext { /* Indexed by base face index, returns first ptex face index corresponding * to that base face. */ int *face_ptex_offset; + + /* Vertex crease custom data layer, null if none is present. */ + const float *cd_vertex_crease; } MultiresReshapeContext; /** @@ -143,15 +146,19 @@ typedef struct ReshapeConstGridElement { * Construct/destruct reshape context. */ -/* Create subdivision surface descriptor which is configured for surface evaluation at a given - * multires modifier. */ +/** + * Create subdivision surface descriptor which is configured for surface evaluation at a given + * multi-res modifier. + */ struct Subdiv *multires_reshape_create_subdiv(struct Depsgraph *depsgraph, struct Object *object, const struct MultiresModifierData *mmd); -/* NOTE: Initialized base mesh to object's mesh, the Subdiv is created from the deformed - * mesh prior to the multires modifier if depsgraph is not NULL. If the depsgraph is NULL - * then Subdiv is created from base mesh (without any deformation applied). */ +/** + * \note Initialized base mesh to object's mesh, the Subdivision is created from the deformed + * mesh prior to the multi-res modifier if depsgraph is not NULL. If the depsgraph is NULL + * then Subdivision is created from base mesh (without any deformation applied). + */ bool multires_reshape_context_create_from_object(MultiresReshapeContext *reshape_context, struct Depsgraph *depsgraph, struct Object *object, @@ -185,44 +192,60 @@ void multires_reshape_context_free(MultiresReshapeContext *reshape_context); * Helper accessors. */ -/* For the given grid index get index of face it was created for. */ +/** + * For the given grid index get index of face it was created for. + */ int multires_reshape_grid_to_face_index(const MultiresReshapeContext *reshape_context, int grid_index); -/* For the given grid index get corner of a face it was created for. */ +/** + * For the given grid index get corner of a face it was created for. + */ int multires_reshape_grid_to_corner(const MultiresReshapeContext *reshape_context, int grid_index); bool multires_reshape_is_quad_face(const MultiresReshapeContext *reshape_context, int face_index); -/* For the given grid index get index of corresponding ptex face. */ +/** + * For the given grid index get index of corresponding PTEX face. + */ int multires_reshape_grid_to_ptex_index(const MultiresReshapeContext *reshape_context, int grid_index); -/* Convert normalized coordinate within a grid to a normalized coordinate within a ptex face. */ +/** + * Convert normalized coordinate within a grid to a normalized coordinate within a PTEX face. + */ PTexCoord multires_reshape_grid_coord_to_ptex(const MultiresReshapeContext *reshape_context, const GridCoord *grid_coord); -/* Convert a normalized coordinate within a ptex face to a normalized coordinate within a grid. */ +/** + * Convert a normalized coordinate within a PTEX face to a normalized coordinate within a grid. + */ GridCoord multires_reshape_ptex_coord_to_grid(const MultiresReshapeContext *reshape_context, const PTexCoord *ptex_coord); -/* Calculate tangent matrix which converts displacement to a object vector. - * Is calculated for the given surface derivatives at a given base face corner. */ +/** + * Calculate tangent matrix which converts displacement to a object vector. + * Is calculated for the given surface derivatives at a given base face corner. + */ void multires_reshape_tangent_matrix_for_corner(const MultiresReshapeContext *reshape_context, - const int face_index, - const int corner, + int face_index, + int corner, const float dPdu[3], const float dPdv[3], float r_tangent_matrix[3][3]); -/* Get grid elements which are to be reshaped at a given or ptex coordinate. - * The data is coming from final custom mdata layers. */ +/** + * Get grid elements which are to be reshaped at a given or PTEX coordinate. + * The data is coming from final custom mdata layers. + */ ReshapeGridElement multires_reshape_grid_element_for_grid_coord( const MultiresReshapeContext *reshape_context, const GridCoord *grid_coord); ReshapeGridElement multires_reshape_grid_element_for_ptex_coord( const MultiresReshapeContext *reshape_context, const PTexCoord *ptex_coord); -/* Get original grid element for the given coordinate. */ +/** + * Get original grid element for the given coordinate. + */ ReshapeConstGridElement multires_reshape_orig_grid_element_for_grid_coord( const MultiresReshapeContext *reshape_context, const GridCoord *grid_coord); @@ -230,8 +253,10 @@ ReshapeConstGridElement multires_reshape_orig_grid_element_for_grid_coord( * Sample limit surface of the base mesh. */ -/* Evaluate limit surface created from base mesh. - * This is the limit surface which defines tangent space for MDisps. */ +/** + * Evaluate limit surface created from base mesh. + * This is the limit surface which defines tangent space for MDisps. + */ void multires_reshape_evaluate_limit_at_grid(const MultiresReshapeContext *reshape_context, const GridCoord *grid_coord, float r_P[3], @@ -241,33 +266,41 @@ void multires_reshape_evaluate_limit_at_grid(const MultiresReshapeContext *resha * Custom data preparation. */ -/* Make sure custom data is allocated for the given level. */ -void multires_reshape_ensure_grids(struct Mesh *mesh, const int level); +/** + * Make sure custom data is allocated for the given level. + */ +void multires_reshape_ensure_grids(struct Mesh *mesh, int level); /* -------------------------------------------------------------------- * Functions specific to reshaping from a set of vertices in a object position. */ -/* Returns truth if all coordinates were assigned. +/** + * Set displacement grids values at a reshape level to a object coordinates of the given source. + * + * \returns truth if all coordinates were assigned. * * False will be returned if the number of vertex coordinates did not match required number of - * vertices at a reshape level. */ + * vertices at a reshape level. + */ bool multires_reshape_assign_final_coords_from_vertcos( const MultiresReshapeContext *reshape_context, const float (*vert_coords)[3], - const int num_vert_coords); + int num_vert_coords); /* -------------------------------------------------------------------- * Functions specific to reshaping from CCG. */ -/* Store final object-space coordinates in the displacement grids. +/** + * Store final object-space coordinates in the displacement grids. * The reason why displacement grids are used for storage is based on memory * footprint optimization. * - * NOTE: Displacement grids to be at least at a reshape level. + * \note Displacement grids to be at least at a reshape level. * - * Return truth if all coordinates have been updated. */ + * \return truth if all coordinates have been updated. + */ bool multires_reshape_assign_final_coords_from_ccg(const MultiresReshapeContext *reshape_context, struct SubdivCCG *subdiv_ccg); @@ -275,11 +308,15 @@ bool multires_reshape_assign_final_coords_from_ccg(const MultiresReshapeContext * Functions specific to reshaping from MDISPS. */ -/* Reads and writes to the current mesh CD_MDISPS. */ +/** + * Reads and writes to the current mesh #CD_MDISPS. + */ void multires_reshape_assign_final_coords_from_mdisps( const MultiresReshapeContext *reshape_context); -/* Reads from original CD_MIDTSPS, writes to the current mesh CD_MDISPS. */ +/** + * Reads from original #CD_MIDTSPS, writes to the current mesh #CD_MDISPS. + */ void multires_reshape_assign_final_elements_from_orig_mdisps( const MultiresReshapeContext *reshape_context); @@ -287,28 +324,33 @@ void multires_reshape_assign_final_elements_from_orig_mdisps( * Displacement smooth. */ -/* Operates on a displacement grids (CD_MDISPS) which contains object space coordinates stored for +/** + * Operates on a displacement grids (CD_MDISPS) which contains object space coordinates stored for * the reshape level. * * The result is grids which are defining mesh with a smooth surface and details starting from - * reshape level up to top level added back from original displacement grids. */ + * reshape level up to top level added back from original displacement grids. + */ void multires_reshape_smooth_object_grids_with_details( const MultiresReshapeContext *reshape_context); -/* Operates on a displacement grids (CD_MDISPS) which contains object space-coordinates stored for +/** + * Operates on a displacement grids (CD_MDISPS) which contains object space-coordinates stored for * the reshape level. * * Makes it so surface on top level looks smooth. Details are not preserved */ void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_context, - const enum eMultiresSubdivideModeType mode); + enum eMultiresSubdivideModeType mode); /* -------------------------------------------------------------------- * Displacement, space conversion. */ -/* Store original grid data, so then it's possible to calculate delta from it and add - * high-frequency content on top of reshaped grids. */ +/** + * Store original grid data, so then it's possible to calculate delta from it and add + * high-frequency content on top of reshaped grids. + */ void multires_reshape_store_original_grids(MultiresReshapeContext *reshape_context); void multires_reshape_object_grids_to_tangent_displacement( @@ -318,21 +360,29 @@ void multires_reshape_object_grids_to_tangent_displacement( * Apply base. */ -/* Update mesh coordinates to the final positions of displacement in object space. +/** + * Update mesh coordinates to the final positions of displacement in object space. * This is effectively desired position of base mesh vertices after canceling out displacement. * - * NOTE: Expects that mesh's CD_MDISPS has been set to object space positions. */ + * \note Expects that mesh's CD_MDISPS has been set to object space positions. + */ void multires_reshape_apply_base_update_mesh_coords(MultiresReshapeContext *reshape_context); -/* Perform better fitting of the base mesh so its subdivided version brings vertices to their - * desired locations. */ +/** + * Perform better fitting of the base mesh so its subdivided version brings vertices to their + * desired locations. + */ void multires_reshape_apply_base_refit_base_mesh(MultiresReshapeContext *reshape_context); -/* Refine subdivision surface to the new positions of the base mesh. */ +/** + * Refine subdivision surface to the new positions of the base mesh. + */ void multires_reshape_apply_base_refine_from_base(MultiresReshapeContext *reshape_context); -/* Refine subdivision surface to the new positions of the deformed mesh (base mesh with all - * modifiers leading the multires applied). +/** + * Refine subdivision surface to the new positions of the deformed mesh (base mesh with all + * modifiers leading the multi-res applied). * - * NOTE: Will re-evaluate all leading modifiers, so it's not cheap. */ + * \note Will re-evaluate all leading modifiers, so it's not cheap. + */ void multires_reshape_apply_base_refine_from_deform(MultiresReshapeContext *reshape_context); diff --git a/source/blender/blenkernel/intern/multires_reshape_smooth.c b/source/blender/blenkernel/intern/multires_reshape_smooth.c index 9fb158d2f84..839c457dd84 100644 --- a/source/blender/blenkernel/intern/multires_reshape_smooth.c +++ b/source/blender/blenkernel/intern/multires_reshape_smooth.c @@ -75,6 +75,7 @@ typedef struct Vertex { int num_grid_coords; GridCoord *grid_coords; + float sharpness; bool is_infinite_sharp; } Vertex; @@ -271,7 +272,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; @@ -489,19 +490,33 @@ static int get_reshape_level_resolution(const MultiresReshapeContext *reshape_co return (1 << reshape_context->reshape.level) + 1; } +static bool is_crease_supported(const MultiresReshapeSmoothContext *reshape_smooth_context) +{ + return !ELEM(reshape_smooth_context->smoothing_type, + MULTIRES_SUBDIVIDE_LINEAR, + MULTIRES_SUBDIVIDE_SIMPLE); +} + /* Get crease which will be used for communication to OpenSubdiv topology. * Note that simple subdivision treats all base edges as infinitely sharp. */ -static char get_effective_edge_crease_char( - const MultiresReshapeSmoothContext *reshape_smooth_context, const MEdge *base_edge) +static char get_effective_crease_char(const MultiresReshapeSmoothContext *reshape_smooth_context, + const MEdge *base_edge) { - if (ELEM(reshape_smooth_context->smoothing_type, - MULTIRES_SUBDIVIDE_LINEAR, - MULTIRES_SUBDIVIDE_SIMPLE)) { + if (!is_crease_supported(reshape_smooth_context)) { return 255; } return base_edge->crease; } +static float get_effective_crease_float(const MultiresReshapeSmoothContext *reshape_smooth_context, + const float crease) +{ + if (!is_crease_supported(reshape_smooth_context)) { + return 1.0f; + } + return crease; +} + static void context_init(MultiresReshapeSmoothContext *reshape_smooth_context, const MultiresReshapeContext *reshape_context, const eMultiresSubdivideModeType mode) @@ -566,7 +581,8 @@ static bool foreach_topology_info(const SubdivForeachContext *foreach_context, const int num_vertices, const int num_edges, const int num_loops, - const int num_polygons) + const int num_polygons, + const int *UNUSED(subdiv_polygon_offset)) { MultiresReshapeSmoothContext *reshape_smooth_context = foreach_context->user_data; const int max_edges = reshape_smooth_context->smoothing_type == MULTIRES_SUBDIVIDE_LINEAR ? @@ -576,25 +592,26 @@ 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; } static void foreach_single_vertex(const SubdivForeachContext *foreach_context, const GridCoord *grid_coord, + const int coarse_vertex_index, const int subdiv_vertex_index) { const MultiresReshapeSmoothContext *reshape_smooth_context = foreach_context->user_data; @@ -607,11 +624,32 @@ static void foreach_single_vertex(const SubdivForeachContext *foreach_context, sizeof(Vertex) * (vertex->num_grid_coords + 1)); vertex->grid_coords[vertex->num_grid_coords] = *grid_coord; ++vertex->num_grid_coords; + + if (coarse_vertex_index == -1) { + return; + } + + const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context; + const float *cd_vertex_crease = reshape_context->cd_vertex_crease; + + if (cd_vertex_crease == NULL) { + return; + } + + float crease = cd_vertex_crease[coarse_vertex_index]; + + if (crease == 0.0f) { + return; + } + + crease = get_effective_crease_float(reshape_smooth_context, crease); + vertex->sharpness = BKE_subdiv_crease_to_sharpness_f(crease); } /* TODO(sergey): De-duplicate with similar function in multires_reshape_vertcos.c */ static void foreach_vertex(const SubdivForeachContext *foreach_context, const PTexCoord *ptex_coord, + const int coarse_vertex_index, const int subdiv_vertex_index) { const MultiresReshapeSmoothContext *reshape_smooth_context = foreach_context->user_data; @@ -631,12 +669,13 @@ static void foreach_vertex(const SubdivForeachContext *foreach_context, for (int current_corner = 0; current_corner < num_corners; ++current_corner) { GridCoord corner_grid_coord = grid_coord; corner_grid_coord.grid_index = start_grid_index + current_corner; - foreach_single_vertex(foreach_context, &corner_grid_coord, subdiv_vertex_index); + foreach_single_vertex( + foreach_context, &corner_grid_coord, coarse_vertex_index, subdiv_vertex_index); } return; } - foreach_single_vertex(foreach_context, &grid_coord, subdiv_vertex_index); + foreach_single_vertex(foreach_context, &grid_coord, coarse_vertex_index, subdiv_vertex_index); if (grid_coord.u == 0.0f) { GridCoord prev_grid_coord; @@ -644,7 +683,8 @@ static void foreach_vertex(const SubdivForeachContext *foreach_context, prev_grid_coord.u = grid_coord.v; prev_grid_coord.v = 0.0f; - foreach_single_vertex(foreach_context, &prev_grid_coord, subdiv_vertex_index); + foreach_single_vertex( + foreach_context, &prev_grid_coord, coarse_vertex_index, subdiv_vertex_index); } if (grid_coord.v == 0.0f) { @@ -653,7 +693,8 @@ static void foreach_vertex(const SubdivForeachContext *foreach_context, next_grid_coord.u = 0.0f; next_grid_coord.v = grid_coord.u; - foreach_single_vertex(foreach_context, &next_grid_coord, subdiv_vertex_index); + foreach_single_vertex( + foreach_context, &next_grid_coord, coarse_vertex_index, subdiv_vertex_index); } } @@ -671,7 +712,7 @@ static void foreach_vertex_inner(const struct SubdivForeachContext *foreach_cont .u = ptex_face_u, .v = ptex_face_v, }; - foreach_vertex(foreach_context, &ptex_coord, subdiv_vertex_index); + foreach_vertex(foreach_context, &ptex_coord, -1, subdiv_vertex_index); } static void foreach_vertex_every_corner(const struct SubdivForeachContext *foreach_context, @@ -679,7 +720,7 @@ static void foreach_vertex_every_corner(const struct SubdivForeachContext *forea const int ptex_face_index, const float ptex_face_u, const float ptex_face_v, - const int UNUSED(coarse_vertex_index), + const int coarse_vertex_index, const int UNUSED(coarse_face_index), const int UNUSED(coarse_face_corner), const int subdiv_vertex_index) @@ -689,7 +730,7 @@ static void foreach_vertex_every_corner(const struct SubdivForeachContext *forea .u = ptex_face_u, .v = ptex_face_v, }; - foreach_vertex(foreach_context, &ptex_coord, subdiv_vertex_index); + foreach_vertex(foreach_context, &ptex_coord, coarse_vertex_index, subdiv_vertex_index); } static void foreach_vertex_every_edge(const struct SubdivForeachContext *foreach_context, @@ -707,7 +748,7 @@ static void foreach_vertex_every_edge(const struct SubdivForeachContext *foreach .u = ptex_face_u, .v = ptex_face_v, }; - foreach_vertex(foreach_context, &ptex_coord, subdiv_vertex_index); + foreach_vertex(foreach_context, &ptex_coord, -1, subdiv_vertex_index); } static void foreach_loop(const struct SubdivForeachContext *foreach_context, @@ -777,7 +818,7 @@ static void store_edge(MultiresReshapeSmoothContext *reshape_smooth_context, Edge *edge = &reshape_smooth_context->geometry.edges[edge_index]; edge->v1 = subdiv_v1; edge->v2 = subdiv_v2; - edge->sharpness = BKE_subdiv_edge_crease_to_sharpness_char(crease); + edge->sharpness = BKE_subdiv_crease_to_sharpness_char(crease); } static void foreach_edge(const struct SubdivForeachContext *foreach_context, @@ -808,7 +849,7 @@ static void foreach_edge(const struct SubdivForeachContext *foreach_context, /* Edges without crease are to be ignored as well. */ const Mesh *base_mesh = reshape_context->base_mesh; const MEdge *base_edge = &base_mesh->medge[coarse_edge_index]; - const char crease = get_effective_edge_crease_char(reshape_smooth_context, base_edge); + const char crease = get_effective_crease_char(reshape_smooth_context, base_edge); if (crease == 0) { return; } @@ -834,8 +875,7 @@ static void geometry_init_loose_information(MultiresReshapeSmoothContext *reshap if (!BLI_BITMAP_TEST_BOOL(reshape_smooth_context->non_loose_base_edge_map, loop->e)) { BLI_BITMAP_ENABLE(reshape_smooth_context->non_loose_base_edge_map, loop->e); - const char crease = get_effective_edge_crease_char(reshape_smooth_context, - &base_edge[loop->e]); + const char crease = get_effective_crease_char(reshape_smooth_context, &base_edge[loop->e]); if (crease != 0) { ++num_used_edges; } @@ -978,6 +1018,15 @@ static float get_edge_sharpness(const OpenSubdiv_Converter *converter, const int return edge->sharpness; } +static float get_vertex_sharpness(const OpenSubdiv_Converter *converter, const int vertex_index) +{ + const MultiresReshapeSmoothContext *reshape_smooth_context = converter->user_data; + BLI_assert(vertex_index < reshape_smooth_context->geometry.num_vertices); + + const Vertex *vertex = &reshape_smooth_context->geometry.vertices[vertex_index]; + return vertex->sharpness; +} + static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter, int vertex_index) { const MultiresReshapeSmoothContext *reshape_smooth_context = converter->user_data; @@ -1014,7 +1063,7 @@ static void converter_init(const MultiresReshapeSmoothContext *reshape_smooth_co converter->getNumVertexFaces = NULL; converter->getVertexFaces = NULL; converter->isInfiniteSharpVertex = is_infinite_sharp_vertex; - converter->getVertexSharpness = NULL; + converter->getVertexSharpness = get_vertex_sharpness; converter->getNumUVLayers = NULL; converter->precalcUVLayer = NULL; @@ -1037,7 +1086,7 @@ static void reshape_subdiv_create(MultiresReshapeSmoothContext *reshape_smooth_c converter_init(reshape_smooth_context, &converter); Subdiv *reshape_subdiv = BKE_subdiv_new_from_converter(settings, &converter); - BKE_subdiv_eval_begin(reshape_subdiv); + BKE_subdiv_eval_begin(reshape_subdiv, SUBDIV_EVALUATOR_TYPE_CPU, NULL); reshape_smooth_context->reshape_subdiv = reshape_subdiv; diff --git a/source/blender/blenkernel/intern/multires_reshape_util.c b/source/blender/blenkernel/intern/multires_reshape_util.c index 8fb406e54a5..810cf328531 100644 --- a/source/blender/blenkernel/intern/multires_reshape_util.c +++ b/source/blender/blenkernel/intern/multires_reshape_util.c @@ -47,8 +47,6 @@ /** \name Construct/destruct reshape context * \{ */ -/* Create subdivision surface descriptor which is configured for surface evaluation at a given - * multires modifier. */ Subdiv *multires_reshape_create_subdiv(Depsgraph *depsgraph, /*const*/ Object *object, const MultiresModifierData *mmd) @@ -67,7 +65,7 @@ Subdiv *multires_reshape_create_subdiv(Depsgraph *depsgraph, SubdivSettings subdiv_settings; BKE_multires_subdiv_settings_init(&subdiv_settings, mmd); Subdiv *subdiv = BKE_subdiv_new_from_mesh(&subdiv_settings, base_mesh); - if (!BKE_subdiv_eval_begin_from_mesh(subdiv, base_mesh, NULL)) { + if (!BKE_subdiv_eval_begin_from_mesh(subdiv, base_mesh, NULL, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) { BKE_subdiv_free(subdiv); return NULL; } @@ -86,7 +84,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 +95,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; @@ -137,7 +135,7 @@ static void context_init_commoon(MultiresReshapeContext *reshape_context) static bool context_is_valid(MultiresReshapeContext *reshape_context) { if (reshape_context->mdisps == NULL) { - /* Multires displacement has been removed before current changes were applies. */ + /* Multi-resolution displacement has been removed before current changes were applies. */ return false; } return true; @@ -213,6 +211,8 @@ bool multires_reshape_context_create_from_object(MultiresReshapeContext *reshape reshape_context->top.level = mmd->totlvl; reshape_context->top.grid_size = BKE_subdiv_grid_size_from_level(reshape_context->top.level); + reshape_context->cd_vertex_crease = CustomData_get_layer(&base_mesh->vdata, CD_CREASE); + context_init_commoon(reshape_context); return context_verify_or_free(reshape_context); @@ -332,7 +332,6 @@ void multires_reshape_context_free(MultiresReshapeContext *reshape_context) /** \name Helper accessors * \{ */ -/* For the given grid index get index of face it was created for. */ int multires_reshape_grid_to_face_index(const MultiresReshapeContext *reshape_context, int grid_index) { @@ -345,7 +344,6 @@ int multires_reshape_grid_to_face_index(const MultiresReshapeContext *reshape_co return reshape_context->grid_to_face_index[grid_index]; } -/* For the given grid index get corner of a face it was created for. */ int multires_reshape_grid_to_corner(const MultiresReshapeContext *reshape_context, int grid_index) { BLI_assert(grid_index >= 0); @@ -364,7 +362,6 @@ bool multires_reshape_is_quad_face(const MultiresReshapeContext *reshape_context return (base_poly->totloop == 4); } -/* For the given grid index get index of corresponding ptex face. */ int multires_reshape_grid_to_ptex_index(const MultiresReshapeContext *reshape_context, int grid_index) { @@ -374,7 +371,6 @@ int multires_reshape_grid_to_ptex_index(const MultiresReshapeContext *reshape_co return reshape_context->face_ptex_offset[face_index] + (is_quad ? 0 : corner); } -/* Convert normalized coordinate within a grid to a normalized coordinate within a ptex face. */ PTexCoord multires_reshape_grid_coord_to_ptex(const MultiresReshapeContext *reshape_context, const GridCoord *grid_coord) { @@ -402,7 +398,6 @@ PTexCoord multires_reshape_grid_coord_to_ptex(const MultiresReshapeContext *resh return ptex_coord; } -/* Convert a normalized coordinate within a ptex face to a normalized coordinate within a grid. */ GridCoord multires_reshape_ptex_coord_to_grid(const MultiresReshapeContext *reshape_context, const PTexCoord *ptex_coord) { diff --git a/source/blender/blenkernel/intern/multires_reshape_vertcos.c b/source/blender/blenkernel/intern/multires_reshape_vertcos.c index 04df5698cf9..c009349ff1b 100644 --- a/source/blender/blenkernel/intern/multires_reshape_vertcos.c +++ b/source/blender/blenkernel/intern/multires_reshape_vertcos.c @@ -114,7 +114,8 @@ static bool multires_reshape_vertcos_foreach_topology_info( const int num_vertices, const int UNUSED(num_edges), const int UNUSED(num_loops), - const int UNUSED(num_polygons)) + const int UNUSED(num_polygons), + const int *UNUSED(subdiv_polygon_offset)) { MultiresReshapeAssignVertcosContext *reshape_vertcos_context = foreach_context->user_data; if (num_vertices != reshape_vertcos_context->num_vert_coords) { @@ -182,7 +183,6 @@ static void multires_reshape_vertcos_foreach_vertex_every_edge( multires_reshape_vertcos_foreach_vertex(foreach_context, &ptex_coord, subdiv_vertex_index); } -/* Set displacement grids values at a reshape level to a object coordinates of the given source. */ bool multires_reshape_assign_final_coords_from_vertcos( const MultiresReshapeContext *reshape_context, const float (*vert_coords)[3], diff --git a/source/blender/blenkernel/intern/multires_unsubdivide.c b/source/blender/blenkernel/intern/multires_unsubdivide.c index 501e3f27389..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; @@ -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/multires_versioning.c b/source/blender/blenkernel/intern/multires_versioning.c index 4c0d7165cd0..18708c43f26 100644 --- a/source/blender/blenkernel/intern/multires_versioning.c +++ b/source/blender/blenkernel/intern/multires_versioning.c @@ -61,7 +61,7 @@ static Subdiv *subdiv_for_simple_to_catmull_clark(Object *object, MultiresModifi Subdiv *subdiv = BKE_subdiv_new_from_converter(&subdiv_settings, &converter); BKE_subdiv_converter_free(&converter); - if (!BKE_subdiv_eval_begin_from_mesh(subdiv, base_mesh, NULL)) { + if (!BKE_subdiv_eval_begin_from_mesh(subdiv, base_mesh, NULL, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) { BKE_subdiv_free(subdiv); return NULL; } diff --git a/source/blender/blenkernel/intern/nla.c b/source/blender/blenkernel/intern/nla.c index 487e925df79..84484a63291 100644 --- a/source/blender/blenkernel/intern/nla.c +++ b/source/blender/blenkernel/intern/nla.c @@ -61,14 +61,20 @@ static CLG_LogRef LOG = {"bke.nla"}; +/** + * Find the active track and strip. + * + * The active strip may or may not be on the active track. + */ +static void nla_tweakmode_find_active(const ListBase /* NlaTrack */ *nla_tracks, + NlaTrack **r_track_of_active_strip, + NlaStrip **r_active_strip); + /* *************************************************** */ /* Data Management */ /* Freeing ------------------------------------------- */ -/* Remove the given NLA strip from the NLA track it occupies, free the strip's data, - * and the strip itself. - */ void BKE_nlastrip_free(ListBase *strips, NlaStrip *strip, bool do_id_user) { NlaStrip *cs, *csn; @@ -108,9 +114,6 @@ void BKE_nlastrip_free(ListBase *strips, NlaStrip *strip, bool do_id_user) } } -/* Remove the given NLA track from the set of NLA tracks, free the track's data, - * and the track itself. - */ void BKE_nlatrack_free(ListBase *tracks, NlaTrack *nlt, bool do_id_user) { NlaStrip *strip, *stripn; @@ -135,9 +138,6 @@ void BKE_nlatrack_free(ListBase *tracks, NlaTrack *nlt, bool do_id_user) } } -/* Free the elements of type NLA Tracks provided in the given list, but do not free - * the list itself since that is not free-standing - */ void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user) { NlaTrack *nlt, *nltn; @@ -159,13 +159,6 @@ void BKE_nla_tracks_free(ListBase *tracks, bool do_id_user) /* Copying ------------------------------------------- */ -/** - * Copy NLA strip - * - * \param use_same_action: When true, the existing action is used (instead of being duplicated) - * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... - * flags in BKE_lib_id.h - */ NlaStrip *BKE_nlastrip_copy(Main *bmain, NlaStrip *strip, const bool use_same_action, @@ -215,11 +208,6 @@ NlaStrip *BKE_nlastrip_copy(Main *bmain, return strip_d; } -/** - * Copy a single NLA Track. - * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... - * flags in BKE_lib_id.h - */ NlaTrack *BKE_nlatrack_copy(Main *bmain, NlaTrack *nlt, const bool use_same_actions, @@ -249,11 +237,6 @@ NlaTrack *BKE_nlatrack_copy(Main *bmain, return nlt_d; } -/** - * Copy all NLA data. - * \param flag: Control ID pointers management, see LIB_ID_CREATE_.../LIB_ID_COPY_... - * flags in BKE_lib_id.h - */ void BKE_nla_tracks_copy(Main *bmain, ListBase *dst, const ListBase *src, const int flag) { NlaTrack *nlt, *nlt_d; @@ -309,6 +292,14 @@ static void update_active_track(AnimData *adt_dest, const AnimData *adt_source) track_dest = track_dest->next; } + + /* If the above assumption failed to hold, do a more thorough search for the active strip. */ + if (adt_source->actstrip != NULL && adt_dest->actstrip == NULL) { + nla_tweakmode_find_active(&adt_source->nla_tracks, &track_dest, &adt_dest->actstrip); + } + + BLI_assert_msg((adt_source->actstrip == NULL) == (adt_dest->actstrip == NULL), + "Active strip did not copy correctly"); } void BKE_nla_tracks_copy_from_adt(Main *bmain, @@ -325,9 +316,6 @@ void BKE_nla_tracks_copy_from_adt(Main *bmain, /* Adding ------------------------------------------- */ -/* Add a NLA Track to the given AnimData - * - prev: NLA-Track to add the new one after - */ NlaTrack *BKE_nlatrack_add(AnimData *adt, NlaTrack *prev, const bool is_liboverride) { NlaTrack *nlt; @@ -371,7 +359,6 @@ NlaTrack *BKE_nlatrack_add(AnimData *adt, NlaTrack *prev, const bool is_liboverr return nlt; } -/* Create a NLA Strip referencing the given Action */ NlaStrip *BKE_nlastrip_new(bAction *act) { NlaStrip *strip; @@ -390,6 +377,16 @@ NlaStrip *BKE_nlastrip_new(bAction *act) */ strip->flag = NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_SYNC_LENGTH; + /* Disable sync for actions with a manual frame range, since it only syncs to range anyway. */ + if (act->flag & ACT_FRAME_RANGE) { + strip->flag &= ~NLASTRIP_FLAG_SYNC_LENGTH; + } + + /* Enable cyclic time for known cyclic actions. */ + if (BKE_action_is_cyclic(act)) { + strip->flag |= NLASTRIP_FLAG_USR_TIME_CYCLIC; + } + /* assign the action reference */ strip->act = act; id_us_plus(&act->id); @@ -397,7 +394,7 @@ NlaStrip *BKE_nlastrip_new(bAction *act) /* determine initial range * - strip length cannot be 0... ever... */ - calc_action_range(strip->act, &strip->actstart, &strip->actend, 0); + BKE_action_get_frame_range(strip->act, &strip->actstart, &strip->actend); strip->start = strip->actstart; strip->end = (IS_EQF(strip->actstart, strip->actend)) ? (strip->actstart + 1.0f) : @@ -411,8 +408,6 @@ NlaStrip *BKE_nlastrip_new(bAction *act) return strip; } -/* Add new NLA-strip to the top of the NLA stack - i.e. - * into the last track if space, or a new one otherwise. */ NlaStrip *BKE_nlastack_add_strip(AnimData *adt, bAction *act, const bool is_liboverride) { NlaStrip *strip; @@ -445,7 +440,6 @@ NlaStrip *BKE_nlastack_add_strip(AnimData *adt, bAction *act, const bool is_libo return strip; } -/* Add a NLA Strip referencing the given speaker's sound */ NlaStrip *BKE_nla_add_soundstrip(Main *bmain, Scene *scene, Speaker *speaker) { NlaStrip *strip = MEM_callocN(sizeof(NlaStrip), "NlaSoundStrip"); @@ -482,20 +476,16 @@ NlaStrip *BKE_nla_add_soundstrip(Main *bmain, Scene *scene, Speaker *speaker) return strip; } -/** - * Callback used by lib_query to walk over all ID usages (mimics `foreach_id` callback of - * `IDTypeInfo` structure). - */ 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)); } } @@ -601,12 +591,6 @@ static float nlastrip_get_frame_transition(NlaStrip *strip, float cframe, short return (cframe - strip->start) / length; } -/* non clipped mapping for strip-time <-> global time - * mode = eNlaTime_ConvertModes[] -> NLATIME_CONVERT_* - * - * only secure for 'internal' (i.e. within AnimSys evaluation) operations, - * but should not be directly relied on for stuff which interacts with editors - */ float nlastrip_get_frame(NlaStrip *strip, float cframe, short mode) { switch (strip->type) { @@ -621,12 +605,6 @@ float nlastrip_get_frame(NlaStrip *strip, float cframe, short mode) } } -/* Non clipped mapping for strip-time <-> global time - * mode = eNlaTime_ConvertModes -> NLATIME_CONVERT_* - * - * Public API method - perform this mapping using the given AnimData block - * and perform any necessary sanity checks on the value - */ float BKE_nla_tweakedit_remap(AnimData *adt, float cframe, short mode) { NlaStrip *strip; @@ -675,7 +653,6 @@ float BKE_nla_tweakedit_remap(AnimData *adt, float cframe, short mode) /* List of Strips ------------------------------------ */ /* (these functions are used for NLA-Tracks and also for nested/meta-strips) */ -/* Check if there is any space in the given list to add the given strip */ bool BKE_nlastrips_has_space(ListBase *strips, float start, float end) { NlaStrip *strip; @@ -710,9 +687,6 @@ bool BKE_nlastrips_has_space(ListBase *strips, float start, float end) return true; } -/* Rearrange the strips in the track so that they are always in order - * (usually only needed after a strip has been moved) - */ void BKE_nlastrips_sort_strips(ListBase *strips) { ListBase tmp = {NULL, NULL}; @@ -756,9 +730,6 @@ void BKE_nlastrips_sort_strips(ListBase *strips) strips->last = tmp.last; } -/* Add the given NLA-Strip to the given list of strips, assuming that it - * isn't currently a member of another list - */ bool BKE_nlastrips_add_strip(ListBase *strips, NlaStrip *strip) { NlaStrip *ns; @@ -794,10 +765,6 @@ bool BKE_nlastrips_add_strip(ListBase *strips, NlaStrip *strip) /* Meta-Strips ------------------------------------ */ -/* Convert 'islands' (i.e. continuous string of) selected strips to be - * contained within 'Meta-Strips' which act as strips which contain strips. - * temp: are the meta-strips to be created 'temporary' ones used for transforms? - */ void BKE_nlastrips_make_metas(ListBase *strips, bool is_temp) { NlaStrip *mstrip = NULL; @@ -851,7 +818,6 @@ void BKE_nlastrips_make_metas(ListBase *strips, bool is_temp) } } -/* Split a meta-strip into a set of normal strips */ void BKE_nlastrips_clear_metastrip(ListBase *strips, NlaStrip *strip) { NlaStrip *cs, *csn; @@ -874,10 +840,6 @@ void BKE_nlastrips_clear_metastrip(ListBase *strips, NlaStrip *strip) BKE_nlastrip_free(strips, strip, true); } -/* Remove meta-strips (i.e. flatten the list of strips) from the top-level of the list of strips - * sel: only consider selected meta-strips, otherwise all meta-strips are removed - * onlyTemp: only remove the 'temporary' meta-strips used for transforms - */ void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp) { NlaStrip *strip, *stripn; @@ -903,9 +865,6 @@ void BKE_nlastrips_clear_metas(ListBase *strips, bool only_sel, bool only_temp) } } -/* Add the given NLA-Strip to the given Meta-Strip, assuming that the - * strip isn't attached to any list of strips - */ bool BKE_nlameta_add_strip(NlaStrip *mstrip, NlaStrip *strip) { /* sanity checks */ @@ -954,9 +913,6 @@ bool BKE_nlameta_add_strip(NlaStrip *mstrip, NlaStrip *strip) return BKE_nlastrips_add_strip(&mstrip->strips, strip); } -/* Adjust the settings of NLA-Strips contained within a Meta-Strip (recursively), - * until the Meta-Strips children all fit within the Meta-Strip's new dimensions - */ void BKE_nlameta_flush_transforms(NlaStrip *mstrip) { NlaStrip *strip; @@ -1039,7 +995,6 @@ void BKE_nlameta_flush_transforms(NlaStrip *mstrip) /* NLA-Tracks ---------------------------------------- */ -/* Find the active NLA-track for the given stack */ NlaTrack *BKE_nlatrack_find_active(ListBase *tracks) { NlaTrack *nlt; @@ -1060,11 +1015,6 @@ NlaTrack *BKE_nlatrack_find_active(ListBase *tracks) return NULL; } -/* Get the NLA Track that the active action/action strip comes from, - * since this info is not stored in AnimData. It also isn't as simple - * as just using the active track, since multiple tracks may have been - * entered at the same time. - */ NlaTrack *BKE_nlatrack_find_tweaked(AnimData *adt) { NlaTrack *nlt; @@ -1096,9 +1046,6 @@ NlaTrack *BKE_nlatrack_find_tweaked(AnimData *adt) return NULL; } -/* Toggle the 'solo' setting for the given NLA-track, making sure that it is the only one - * that has this status in its AnimData block. - */ void BKE_nlatrack_solo_toggle(AnimData *adt, NlaTrack *nlt) { NlaTrack *nt; @@ -1133,9 +1080,6 @@ void BKE_nlatrack_solo_toggle(AnimData *adt, NlaTrack *nlt) } } -/* Make the given NLA-track the active one for the given stack. If no track is provided, - * this function can be used to simply deactivate all the NLA tracks in the given stack too. - */ void BKE_nlatrack_set_active(ListBase *tracks, NlaTrack *nlt_a) { NlaTrack *nlt; @@ -1156,7 +1100,6 @@ void BKE_nlatrack_set_active(ListBase *tracks, NlaTrack *nlt_a) } } -/* Check if there is any space in the given track to add a strip of the given length */ bool BKE_nlatrack_has_space(NlaTrack *nlt, float start, float end) { /* sanity checks @@ -1177,9 +1120,6 @@ bool BKE_nlatrack_has_space(NlaTrack *nlt, float start, float end) return BKE_nlastrips_has_space(&nlt->strips, start, end); } -/* Rearrange the strips in the track so that they are always in order - * (usually only needed after a strip has been moved) - */ void BKE_nlatrack_sort_strips(NlaTrack *nlt) { /* sanity checks */ @@ -1191,9 +1131,6 @@ void BKE_nlatrack_sort_strips(NlaTrack *nlt) BKE_nlastrips_sort_strips(&nlt->strips); } -/* Add the given NLA-Strip to the given NLA-Track, assuming that it - * isn't currently attached to another one - */ bool BKE_nlatrack_add_strip(NlaTrack *nlt, NlaStrip *strip, const bool is_liboverride) { /* sanity checks */ @@ -1211,9 +1148,6 @@ bool BKE_nlatrack_add_strip(NlaTrack *nlt, NlaStrip *strip, const bool is_libove return BKE_nlastrips_add_strip(&nlt->strips, strip); } -/* Get the extents of the given NLA-Track including gaps between strips, - * returning whether this succeeded or not - */ bool BKE_nlatrack_get_bounds(NlaTrack *nlt, float bounds[2]) { NlaStrip *strip; @@ -1243,12 +1177,6 @@ bool BKE_nlatrack_get_bounds(NlaTrack *nlt, float bounds[2]) return true; } -/** - * Check whether given NLA track is not local (i.e. from linked data) when the object is a library - * override. - * - * \param nlt: May be NULL, in which case we consider it as a non-local track case. - */ bool BKE_nlatrack_is_nonlocal_in_liboverride(const ID *id, const NlaTrack *nlt) { return (ID_IS_OVERRIDE_LIBRARY(id) && @@ -1257,7 +1185,6 @@ bool BKE_nlatrack_is_nonlocal_in_liboverride(const ID *id, const NlaTrack *nlt) /* NLA Strips -------------------------------------- */ -/* Find the active NLA-strip within the given track */ NlaStrip *BKE_nlastrip_find_active(NlaTrack *nlt) { NlaStrip *strip; @@ -1278,7 +1205,6 @@ NlaStrip *BKE_nlastrip_find_active(NlaTrack *nlt) return NULL; } -/* Make the given NLA-Strip the active one within the given block */ void BKE_nlastrip_set_active(AnimData *adt, NlaStrip *strip) { NlaTrack *nlt; @@ -1302,7 +1228,6 @@ void BKE_nlastrip_set_active(AnimData *adt, NlaStrip *strip) } } -/* Does the given NLA-strip fall within the given bounds (times)? */ bool BKE_nlastrip_within_bounds(NlaStrip *strip, float min, float max) { const float stripLen = (strip) ? strip->end - strip->start : 0.0f; @@ -1430,10 +1355,6 @@ static void nlastrip_fix_resize_overlaps(NlaStrip *strip) } } -/** - * Recalculate the start and end frames for the strip to match the bounds of its action such that - * the overall NLA animation result is unchanged. - */ void BKE_nlastrip_recalculate_bounds_sync_action(NlaStrip *strip) { float prev_actstart; @@ -1444,16 +1365,13 @@ void BKE_nlastrip_recalculate_bounds_sync_action(NlaStrip *strip) prev_actstart = strip->actstart; - calc_action_range(strip->act, &strip->actstart, &strip->actend, 0); + BKE_action_get_frame_range(strip->act, &strip->actstart, &strip->actend); /* Set start such that key's do not visually move, to preserve the overall animation result. */ strip->start += (strip->actstart - prev_actstart) * strip->scale; BKE_nlastrip_recalculate_bounds(strip); } -/* Recalculate the start and end frames for the current strip, after changing - * the extents of the action or the mapping (repeats or scale factor) info - */ void BKE_nlastrip_recalculate_bounds(NlaStrip *strip) { float actlen, mapping; @@ -1518,7 +1436,6 @@ static bool nlastrip_is_first(AnimData *adt, NlaStrip *strip) /* Animated Strips ------------------------------------------- */ -/* Check if the given NLA-Track has any strips with own F-Curves */ bool BKE_nlatrack_has_animated_strips(NlaTrack *nlt) { NlaStrip *strip; @@ -1539,7 +1456,6 @@ bool BKE_nlatrack_has_animated_strips(NlaTrack *nlt) return false; } -/* Check if given NLA-Tracks have any strips with own F-Curves */ bool BKE_nlatracks_have_animated_strips(ListBase *tracks) { NlaTrack *nlt; @@ -1560,7 +1476,6 @@ bool BKE_nlatracks_have_animated_strips(ListBase *tracks) return false; } -/* Validate the NLA-Strips 'control' F-Curves based on the flags set. */ void BKE_nlastrip_validate_fcurves(NlaStrip *strip) { FCurve *fcu; @@ -1624,9 +1539,6 @@ void BKE_nlastrip_validate_fcurves(NlaStrip *strip) } } -/* Check if the given RNA pointer + property combo should be handled by - * NLA strip curves or not. - */ bool BKE_nlastrip_has_curves_for_property(const PointerRNA *ptr, const PropertyRNA *prop) { /* sanity checks */ @@ -1666,11 +1578,6 @@ static bool nla_editbone_name_check(void *arg, const char *name) return BLI_ghash_haskey((GHash *)arg, (const void *)name); } -/* Find (and set) a unique name for a strip from the whole AnimData block - * Uses a similar method to the BLI method, but is implemented differently - * as we need to ensure that the name is unique over several lists of tracks, - * not just a single track. - */ void BKE_nlastrip_validate_name(AnimData *adt, NlaStrip *strip) { GHash *gh; @@ -1844,7 +1751,6 @@ static void BKE_nlastrip_validate_autoblends(NlaTrack *nlt, NlaStrip *nls) } } -/* Ensure that auto-blending and other settings are set correctly */ void BKE_nla_validate_state(AnimData *adt) { NlaStrip *strip, *fstrip = NULL; @@ -1901,12 +1807,6 @@ void BKE_nla_validate_state(AnimData *adt) /* name of stashed tracks - the translation stuff is included here to save extra work */ #define STASH_TRACK_NAME DATA_("[Action Stash]") -/* Check if an action is "stashed" in the NLA already - * - * The criteria for this are: - * 1) The action in question lives in a "stash" track - * 2) We only check first-level strips. That is, we will not check inside meta strips. - */ bool BKE_nla_action_is_stashed(AnimData *adt, bAction *act) { NlaTrack *nlt; @@ -1925,9 +1825,6 @@ bool BKE_nla_action_is_stashed(AnimData *adt, bAction *act) return false; } -/* "Stash" an action (i.e. store it as a track/layer in the NLA, but non-contributing) - * to retain it in the file for future uses - */ bool BKE_nla_action_stash(AnimData *adt, const bool is_liboverride) { NlaTrack *prev_track = NULL; @@ -1996,12 +1893,6 @@ bool BKE_nla_action_stash(AnimData *adt, const bool is_liboverride) /* Core Tools ------------------------------------------- */ -/* For the given AnimData block, add the active action to the NLA - * stack (i.e. 'push-down' action). The UI should only allow this - * for normal editing only (i.e. not in editmode for some strip's action), - * so no checks for this are performed. - */ -/* TODO: maybe we should have checks for this too... */ void BKE_nla_action_pushdown(AnimData *adt, const bool is_liboverride) { NlaStrip *strip; @@ -2076,29 +1967,17 @@ void BKE_nla_action_pushdown(AnimData *adt, const bool is_liboverride) BKE_nlastrip_set_active(adt, strip); } -/* Find the active strip + track combo, and set them up as the tweaking track, - * and return if successful or not. - */ -bool BKE_nla_tweakmode_enter(AnimData *adt) +static void nla_tweakmode_find_active(const ListBase /* NlaTrack */ *nla_tracks, + NlaTrack **r_track_of_active_strip, + NlaStrip **r_active_strip) { NlaTrack *nlt, *activeTrack = NULL; NlaStrip *strip, *activeStrip = NULL; - /* verify that data is valid */ - if (ELEM(NULL, adt, adt->nla_tracks.first)) { - return false; - } - - /* 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; - } - /* go over the tracks, finding the active one, and its active strip * - if we cannot find both, then there's nothing to do */ - for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) { + for (nlt = nla_tracks->first; nlt; nlt = nlt->next) { /* check if active */ if (nlt->flag & NLATRACK_ACTIVE) { /* store reference to this active track */ @@ -2117,7 +1996,7 @@ bool BKE_nla_tweakmode_enter(AnimData *adt) */ if (activeTrack == NULL) { /* try last selected track for active strip */ - for (nlt = adt->nla_tracks.last; nlt; nlt = nlt->prev) { + for (nlt = nla_tracks->last; nlt; nlt = nlt->prev) { if (nlt->flag & NLATRACK_SELECTED) { /* assume this is the active track */ activeTrack = nlt; @@ -2139,6 +2018,28 @@ bool BKE_nla_tweakmode_enter(AnimData *adt) } } + *r_track_of_active_strip = activeTrack; + *r_active_strip = activeStrip; +} + +bool BKE_nla_tweakmode_enter(AnimData *adt) +{ + NlaTrack *nlt, *activeTrack = NULL; + NlaStrip *strip, *activeStrip = NULL; + + /* verify that data is valid */ + if (ELEM(NULL, adt, adt->nla_tracks.first)) { + return false; + } + + /* 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; + } + + nla_tweakmode_find_active(&adt->nla_tracks, &activeTrack, &activeStrip); + if (ELEM(NULL, activeTrack, activeStrip, activeStrip->act)) { if (G.debug & G_DEBUG) { printf("NLA tweak-mode enter - neither active requirement found\n"); @@ -2191,7 +2092,6 @@ bool BKE_nla_tweakmode_enter(AnimData *adt) return true; } -/* Exit tweak-mode for this AnimData block. */ void BKE_nla_tweakmode_exit(AnimData *adt) { NlaStrip *strip; diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index a3a82bee8dc..40d0c24c9af 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -47,34 +47,36 @@ #include "DNA_texture_types.h" #include "DNA_world_types.h" +#include "BLI_color.hh" #include "BLI_ghash.h" #include "BLI_listbase.h" #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_threads.h" #include "BLI_utildefines.h" #include "BLI_vector_set.hh" - #include "BLT_translation.h" #include "BKE_anim_data.h" #include "BKE_animsys.h" +#include "BKE_bpath.h" #include "BKE_colortools.h" +#include "BKE_context.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_tree_update.h" -#include "BLI_ghash.h" -#include "BLI_threads.h" #include "RNA_access.h" #include "RNA_define.h" @@ -98,10 +100,12 @@ #define NODE_DEFAULT_MAX_WIDTH 700 using blender::Array; +using blender::Map; using blender::MutableSpan; using blender::Set; using blender::Span; using blender::Stack; +using blender::StringRef; using blender::Vector; using blender::VectorSet; using blender::nodes::FieldInferencingInterface; @@ -129,10 +133,6 @@ 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) { @@ -154,62 +154,42 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c BLI_listbase_clear(&ntree_dst->nodes); BLI_listbase_clear(&ntree_dst->links); - /* Since source nodes and sockets are unique pointers we can put everything in a single map. */ - GHash *new_pointers = BLI_ghash_ptr_new(__func__); + Map<const bNode *, bNode *> node_map; + Map<const bNodeSocket *, bNodeSocket *> socket_map; - LISTBASE_FOREACH (const bNode *, node_src, &ntree_src->nodes) { - bNode *new_node = BKE_node_copy_ex(ntree_dst, node_src, flag_subdata, true); - BLI_ghash_insert(new_pointers, (void *)node_src, new_node); - /* Store mapping to inputs. */ - bNodeSocket *new_input_sock = (bNodeSocket *)new_node->inputs.first; - const bNodeSocket *input_sock_src = (const bNodeSocket *)node_src->inputs.first; - while (new_input_sock != nullptr) { - BLI_ghash_insert(new_pointers, (void *)input_sock_src, new_input_sock); - new_input_sock = new_input_sock->next; - input_sock_src = input_sock_src->next; - } - /* Store mapping to outputs. */ - bNodeSocket *new_output_sock = (bNodeSocket *)new_node->outputs.first; - const bNodeSocket *output_sock_src = (const bNodeSocket *)node_src->outputs.first; - while (new_output_sock != nullptr) { - BLI_ghash_insert(new_pointers, (void *)output_sock_src, new_output_sock); - new_output_sock = new_output_sock->next; - output_sock_src = output_sock_src->next; - } + BLI_listbase_clear(&ntree_dst->nodes); + LISTBASE_FOREACH (const bNode *, src_node, &ntree_src->nodes) { + /* Don't find a unique name for every node, since they should have valid names already. */ + bNode *new_node = blender::bke::node_copy_with_mapping( + ntree_dst, *src_node, flag_subdata, false, socket_map); + node_map.add(src_node, new_node); } /* copy links */ - BLI_duplicatelist(&ntree_dst->links, &ntree_src->links); - LISTBASE_FOREACH (bNodeLink *, link_dst, &ntree_dst->links) { - link_dst->fromnode = (bNode *)BLI_ghash_lookup_default( - new_pointers, link_dst->fromnode, nullptr); - link_dst->fromsock = (bNodeSocket *)BLI_ghash_lookup_default( - new_pointers, link_dst->fromsock, nullptr); - link_dst->tonode = (bNode *)BLI_ghash_lookup_default(new_pointers, link_dst->tonode, nullptr); - link_dst->tosock = (bNodeSocket *)BLI_ghash_lookup_default( - new_pointers, link_dst->tosock, nullptr); - /* update the link socket's pointer */ - if (link_dst->tosock) { - link_dst->tosock->link = link_dst; - } + BLI_listbase_clear(&ntree_dst->links); + LISTBASE_FOREACH (const bNodeLink *, src_link, &ntree_src->links) { + bNodeLink *dst_link = (bNodeLink *)MEM_dupallocN(src_link); + dst_link->fromnode = node_map.lookup(src_link->fromnode); + dst_link->fromsock = socket_map.lookup(src_link->fromsock); + dst_link->tonode = node_map.lookup(src_link->tonode); + dst_link->tosock = socket_map.lookup(src_link->tosock); + BLI_assert(dst_link->tosock); + dst_link->tosock->link = dst_link; + BLI_addtail(&ntree_dst->links, dst_link); } /* copy interface sockets */ - BLI_duplicatelist(&ntree_dst->inputs, &ntree_src->inputs); - bNodeSocket *sock_dst, *sock_src; - for (sock_dst = (bNodeSocket *)ntree_dst->inputs.first, - sock_src = (bNodeSocket *)ntree_src->inputs.first; - sock_dst != nullptr; - sock_dst = (bNodeSocket *)sock_dst->next, sock_src = (bNodeSocket *)sock_src->next) { - node_socket_copy(sock_dst, sock_src, flag_subdata); + BLI_listbase_clear(&ntree_dst->inputs); + LISTBASE_FOREACH (const bNodeSocket *, src_socket, &ntree_src->inputs) { + bNodeSocket *dst_socket = (bNodeSocket *)MEM_dupallocN(src_socket); + node_socket_copy(dst_socket, src_socket, flag_subdata); + BLI_addtail(&ntree_dst->inputs, dst_socket); } - - BLI_duplicatelist(&ntree_dst->outputs, &ntree_src->outputs); - for (sock_dst = (bNodeSocket *)ntree_dst->outputs.first, - sock_src = (bNodeSocket *)ntree_src->outputs.first; - sock_dst != nullptr; - sock_dst = (bNodeSocket *)sock_dst->next, sock_src = (bNodeSocket *)sock_src->next) { - node_socket_copy(sock_dst, sock_src, flag_subdata); + BLI_listbase_clear(&ntree_dst->outputs); + LISTBASE_FOREACH (const bNodeSocket *, src_socket, &ntree_src->outputs) { + bNodeSocket *dst_socket = (bNodeSocket *)MEM_dupallocN(src_socket); + node_socket_copy(dst_socket, src_socket, flag_subdata); + BLI_addtail(&ntree_dst->outputs, dst_socket); } /* copy preview hash */ @@ -229,25 +209,25 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c } /* update node->parent pointers */ - for (bNode *node_dst = (bNode *)ntree_dst->nodes.first, - *node_src = (bNode *)ntree_src->nodes.first; - node_dst; - node_dst = (bNode *)node_dst->next, node_src = (bNode *)node_src->next) { - if (node_dst->parent) { - node_dst->parent = (bNode *)BLI_ghash_lookup_default( - new_pointers, node_dst->parent, nullptr); + LISTBASE_FOREACH (bNode *, new_node, &ntree_dst->nodes) { + if (new_node->parent) { + new_node->parent = node_map.lookup(new_node->parent); } } - - BLI_ghash_free(new_pointers, nullptr, nullptr); - /* node tree will generate its own interface type */ ntree_dst->interface_type = nullptr; if (ntree_src->field_inferencing_interface) { - ntree_dst->field_inferencing_interface = node_field_inferencing_interface_copy( + ntree_dst->field_inferencing_interface = new FieldInferencingInterface( *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) @@ -257,8 +237,7 @@ static void ntree_free_data(ID *id) /* XXX hack! node trees should not store execution graphs at all. * This should be removed when old tree types no longer require it. * Currently the execution data for texture nodes remains in the tree - * after execution, until the node tree is updated or freed. - */ + * after execution, until the node tree is updated or freed. */ if (ntree->execdata) { switch (ntree->type) { case NTREE_SHADER: @@ -274,10 +253,10 @@ static void ntree_free_data(ID *id) /* XXX not nice, but needed to free localized node groups properly */ free_localized_node_groups(ntree); - /* unregister associated RNA types */ + /* Unregister associated RNA types. */ ntreeInterfaceTypeFree(ntree); - BLI_freelistN(&ntree->links); /* do first, then unlink_node goes fast */ + BLI_freelistN(&ntree->links); LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) { node_free_node(ntree, node); @@ -293,7 +272,7 @@ static void ntree_free_data(ID *id) MEM_freeN(sock); } - node_field_inferencing_interface_free(ntree->field_inferencing_interface); + delete ntree->field_inferencing_interface; /* free preview hash */ if (ntree->previews) { @@ -303,38 +282,42 @@ static void ntree_free_data(ID *id) if (ntree->id.tag & LIB_TAG_LOCALIZED) { BKE_libblock_free_data(&ntree->id, true); } + + 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: @@ -355,26 +338,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)); } } @@ -404,6 +391,29 @@ static void node_foreach_cache(ID *id, } } +static void node_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + bNodeTree *ntree = reinterpret_cast<bNodeTree *>(id); + + switch (ntree->type) { + case NTREE_SHADER: { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type == SH_NODE_SCRIPT) { + NodeShaderScript *nss = reinterpret_cast<NodeShaderScript *>(node->storage); + BKE_bpath_foreach_path_fixed_process(bpath_data, nss->filepath); + } + else if (node->type == SH_NODE_TEX_IES) { + NodeShaderTexIES *ies = reinterpret_cast<NodeShaderTexIES *>(node->storage); + BKE_bpath_foreach_path_fixed_process(bpath_data, ies->filepath); + } + } + break; + } + default: + break; + } +} + static ID *node_owner_get(Main *bmain, ID *id) { if ((id->flag & LIB_EMBEDDED_DATA) == 0) { @@ -474,8 +484,10 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so case SOCK_MATERIAL: BLO_write_struct(writer, bNodeSocketValueMaterial, sock->default_value); break; - case __SOCK_MESH: case SOCK_CUSTOM: + /* Custom node sockets where default_value is defined uses custom properties for storage. */ + break; + case __SOCK_MESH: case SOCK_SHADER: case SOCK_GEOMETRY: BLI_assert_unreachable(); @@ -485,7 +497,6 @@ static void write_node_socket_default_value(BlendWriter *writer, bNodeSocket *so static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -496,7 +507,6 @@ static void write_node_socket(BlendWriter *writer, bNodeSocket *sock) } static void write_node_socket_interface(BlendWriter *writer, bNodeSocket *sock) { - /* actual socket writing */ BLO_write_struct(writer, bNodeSocket, sock); if (sock->prop) { @@ -506,13 +516,10 @@ static void write_node_socket_interface(BlendWriter *writer, bNodeSocket *sock) write_node_socket_default_value(writer, sock); } -/* this is only direct data, tree itself should have been written */ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) { BKE_id_blend_write(writer, &ntree->id); - /* for link_list() speed, we write per list */ - if (ntree->adt) { BKE_animdata_blend_write(writer, ntree->adt); } @@ -536,9 +543,8 @@ 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)) { + 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) && @@ -610,13 +616,13 @@ void ntreeBlendWrite(BlendWriter *writer, bNodeTree *ntree) } if (node->type == CMP_NODE_OUTPUT_FILE) { - /* inputs have own storage data */ + /* Inputs have their own storage data. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { BLO_write_struct(writer, NodeImageMultiFileSocket, sock->storage); } } if (ELEM(node->type, CMP_NODE_IMAGE, CMP_NODE_R_LAYERS)) { - /* write extra socket info */ + /* Write extra socket info. */ LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { BLO_write_struct(writer, NodeImageLayer, sock->storage); } @@ -633,6 +639,8 @@ 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) @@ -663,9 +671,9 @@ 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. */ @@ -678,6 +686,7 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) ntree->execdata = nullptr; ntree->field_inferencing_interface = nullptr; + BKE_ntree_update_tag_missing_runtime_data(ntree); BLO_read_data_address(reader, &ntree->adt); BKE_animdata_blend_read_data(reader, ntree->adt); @@ -710,10 +719,10 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) } if (node->storage) { - /* could be handlerized at some point */ 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: @@ -747,13 +756,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; } @@ -762,7 +769,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; } @@ -776,7 +782,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) } case TEX_NODE_IMAGE: { ImageUser *iuser = (ImageUser *)node->storage; - iuser->ok = 1; iuser->scene = nullptr; break; } @@ -824,10 +829,8 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree) /* 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 */ } @@ -1030,6 +1033,7 @@ IDTypeInfo IDType_ID_NT = { /* name_plural */ "node_groups", /* translation_context */ BLT_I18NCONTEXT_ID_NODETREE, /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, /* init_data */ ntree_init_data, /* copy_data */ ntree_copy_data, @@ -1037,6 +1041,7 @@ IDTypeInfo IDType_ID_NT = { /* make_local */ nullptr, /* foreach_id */ node_foreach_id, /* foreach_cache */ node_foreach_cache, + /* foreach_path */ node_foreach_path, /* owner_get */ node_owner_get, /* blend_write */ ntree_blend_write, @@ -1052,26 +1057,22 @@ IDTypeInfo IDType_ID_NT = { static void node_add_sockets_from_type(bNodeTree *ntree, bNode *node, bNodeType *ntype) { if (ntype->declare != nullptr) { - nodeDeclarationEnsure(ntree, node); - node->declaration->build(*ntree, *node); + node_verify_sockets(ntree, node, true); return; } bNodeSocketTemplate *sockdef; - /* bNodeSocket *sock; */ /* UNUSED */ if (ntype->inputs) { sockdef = ntype->inputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_IN); sockdef++; } } if (ntype->outputs) { sockdef = ntype->outputs; while (sockdef->type != -1) { - /* sock = */ node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); - + node_add_socket_from_template(ntree, node, sockdef, SOCK_OUT); sockdef++; } } @@ -1129,8 +1130,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` at the moment. - */ + * Delayed init is not supported for nodes with context-based `initfunc_api` at the moment. */ BLI_assert(C != nullptr); ntype->initfunc_api(C, &ptr); } @@ -1142,15 +1142,16 @@ 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; + BKE_ntree_update_tag_all(ntree); } static void node_set_typeinfo(const struct bContext *C, @@ -1201,6 +1202,7 @@ static void node_socket_set_typeinfo(bNodeTree *ntree, ntree->init &= ~NTREE_TYPE_INIT; } + BKE_ntree_update_tag_socket_type(ntree, sock); } /* Set specific typeinfo pointers in all node trees on register/unregister */ @@ -1256,14 +1258,6 @@ static void update_typeinfo(Main *bmain, FOREACH_NODETREE_END; } -/** - * 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 type-info on registration - * and do necessary updates. - */ void ntreeSetTypes(const struct bContext *C, bNodeTree *ntree) { ntree->init |= NTREE_TYPE_INIT; @@ -1335,7 +1329,7 @@ bool ntreeIsRegistered(bNodeTree *ntree) return (ntree->typeinfo != &NodeTreeTypeUndefined); } -GHashIterator *ntreeTypeGetIterator(void) +GHashIterator *ntreeTypeGetIterator() { return BLI_ghashIterator_new(nodetreetypes_hash); } @@ -1352,18 +1346,6 @@ bNodeType *nodeTypeFind(const char *idname) return nullptr; } -static void free_dynamic_typeinfo(bNodeType *ntype) -{ - if (ntype->type == NODE_DYNAMIC) { - if (ntype->inputs) { - MEM_freeN(ntype->inputs); - } - if (ntype->outputs) { - MEM_freeN(ntype->outputs); - } - } -} - /* callback for hash value free function */ static void node_free_type(void *nodetype_v) { @@ -1373,10 +1355,8 @@ static void node_free_type(void *nodetype_v) * or we'd want to update *all* active Mains, which we cannot do anyway currently. */ update_typeinfo(G_MAIN, nullptr, nullptr, nodetype, nullptr, true); - /* XXX deprecated */ - if (nodetype->type == NODE_DYNAMIC) { - 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) { @@ -1390,6 +1370,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 - @@ -1402,14 +1390,14 @@ void nodeUnregisterType(bNodeType *nt) BLI_ghash_remove(nodetypes_hash, nt->idname, nullptr, node_free_type); } -bool nodeTypeUndefined(bNode *node) +bool nodeTypeUndefined(const 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)); } -GHashIterator *nodeTypeGetIterator(void) +GHashIterator *nodeTypeGetIterator() { return BLI_ghashIterator_new(nodetypes_hash); } @@ -1457,7 +1445,7 @@ bool nodeSocketIsRegistered(bNodeSocket *sock) return (sock->typeinfo != &NodeSocketTypeUndefined); } -GHashIterator *nodeSocketTypeGetIterator(void) +GHashIterator *nodeSocketTypeGetIterator() { return BLI_ghashIterator_new(nodesockettypes_hash); } @@ -1481,6 +1469,33 @@ struct bNodeSocket *nodeFindSocket(const bNode *node, return nullptr; } +namespace blender::bke { + +bNodeSocket *node_find_enabled_socket(bNode &node, + const eNodeSocketInOut in_out, + const StringRef name) +{ + ListBase *sockets = (in_out == SOCK_IN) ? &node.inputs : &node.outputs; + LISTBASE_FOREACH (bNodeSocket *, socket, sockets) { + if (!(socket->flag & SOCK_UNAVAIL) && socket->name == name) { + return socket; + } + } + return nullptr; +} + +bNodeSocket *node_find_enabled_input_socket(bNode &node, StringRef name) +{ + return node_find_enabled_socket(node, SOCK_IN, name); +} + +bNodeSocket *node_find_enabled_output_socket(bNode &node, StringRef name) +{ + return node_find_enabled_socket(node, SOCK_OUT, name); +} + +} // namespace blender::bke + /* find unique socket identifier */ static bool unique_identifier_check(void *arg, const char *identifier) { @@ -1511,11 +1526,11 @@ static bNodeSocket *make_socket(bNodeTree *ntree, /* if no explicit identifier is given, assign a unique identifier based on the name */ BLI_strncpy(auto_identifier, name, sizeof(auto_identifier)); } - /* make the identifier unique */ + /* Make the identifier unique. */ BLI_uniquename_cb( - unique_identifier_check, lb, "socket", '.', auto_identifier, sizeof(auto_identifier)); + unique_identifier_check, lb, "socket", '_', auto_identifier, sizeof(auto_identifier)); - bNodeSocket *sock = (bNodeSocket *)MEM_callocN(sizeof(bNodeSocket), "sock"); + bNodeSocket *sock = MEM_cnew<bNodeSocket>("sock"); sock->in_out = in_out; BLI_strncpy(sock->identifier, auto_identifier, NODE_MAXSTR); @@ -1680,26 +1695,7 @@ bNodeSocket *nodeAddSocket(bNodeTree *ntree, BLI_remlink(lb, sock); /* does nothing for new socket */ BLI_addtail(lb, sock); - node->update |= NODE_UPDATE; - - return sock; -} - -bNodeSocket *nodeInsertSocket(bNodeTree *ntree, - bNode *node, - eNodeSocketInOut in_out, - const char *idname, - bNodeSocket *next_sock, - const char *identifier, - const char *name) -{ - ListBase *lb = (in_out == SOCK_IN ? &node->inputs : &node->outputs); - bNodeSocket *sock = make_socket(ntree, node, in_out, lb, idname, identifier, name); - - BLI_remlink(lb, sock); /* does nothing for new socket */ - BLI_insertlinkbefore(lb, next_sock, sock); - - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_new(ntree, sock); return sock; } @@ -1920,31 +1916,7 @@ bNodeSocket *nodeAddStaticSocket(bNodeTree *ntree, return sock; } -bNodeSocket *nodeInsertStaticSocket(bNodeTree *ntree, - bNode *node, - eNodeSocketInOut in_out, - int type, - int subtype, - bNodeSocket *next_sock, - const char *identifier, - const char *name) -{ - const char *idname = nodeStaticSocketType(type, subtype); - - if (!idname) { - CLOG_ERROR(&LOG, "static node socket type %d undefined", type); - return nullptr; - } - - bNodeSocket *sock = nodeInsertSocket(ntree, node, in_out, idname, next_sock, identifier, name); - sock->type = type; - return sock; -} - -static void node_socket_free(bNodeTree *UNUSED(ntree), - bNodeSocket *sock, - bNode *UNUSED(node), - const bool do_id_user) +static void node_socket_free(bNodeSocket *sock, const bool do_id_user) { if (sock->prop) { IDP_FreePropertyContent_ex(sock->prop, do_id_user); @@ -1975,14 +1947,22 @@ void nodeRemoveSocketEx(struct bNodeTree *ntree, } } + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &node->internal_links) { + if (link->fromsock == sock || link->tosock == sock) { + BLI_remlink(&node->internal_links, link); + MEM_freeN(link); + BKE_ntree_update_tag_node_internal_link(ntree, node); + } + } + /* this is fast, this way we don't need an in_out argument */ BLI_remlink(&node->inputs, sock); BLI_remlink(&node->outputs, sock); - node_socket_free(ntree, sock, node, do_id_user); + node_socket_free(sock, do_id_user); MEM_freeN(sock); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) @@ -1994,27 +1974,25 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->inputs); LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { - node_socket_free(ntree, sock, node, true); + node_socket_free(sock, true); MEM_freeN(sock); } BLI_listbase_clear(&node->outputs); - node->update |= NODE_UPDATE; + BKE_ntree_update_tag_socket_removed(ntree); } -/* finds a node based on its name */ bNode *nodeFindNodebyName(bNodeTree *ntree, const char *name) { return (bNode *)BLI_findstring(&ntree->nodes, name, offsetof(bNode, name)); } -/* Finds a node based on given socket and returns true on success. */ bool nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_sockindex) { *r_node = nullptr; @@ -2038,9 +2016,6 @@ bool nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **r_node, int *r_so return false; } -/** - * \note Recursive - */ bNode *nodeFindRootParent(bNode *node) { if (node->parent) { @@ -2049,10 +2024,6 @@ bNode *nodeFindRootParent(bNode *node) return node->type == NODE_FRAME ? node : nullptr; } -/** - * \returns true if \a child has \a parent as a parent/grandparent/... - * \note Recursive - */ bool nodeIsChildOf(const bNode *parent, const bNode *child) { if (parent == child) { @@ -2064,13 +2035,6 @@ bool nodeIsChildOf(const bNode *parent, const bNode *child) return false; } -/** - * Iterate over a chain of nodes, starting with \a node_start, executing - * \a callback for each node (which can return false to end iterator). - * - * \param reversed: for backwards iteration - * \note Recursive - */ void nodeChainIter(const bNodeTree *ntree, const bNode *node_start, bool (*callback)(bNode *, bNode *, void *, const bool), @@ -2125,17 +2089,6 @@ static void iter_backwards_ex(const bNodeTree *ntree, } } -/** - * Iterate over a chain of nodes, starting with \a node_start, executing - * \a callback for each node (which can return false to end iterator). - * - * Faster than nodeChainIter. Iter only once per node. - * Can be called recursively (using another nodeChainIterBackwards) by - * setting the recursion_lvl accordingly. - * - * \note Needs updated socket links (ntreeUpdateTree). - * \note Recursive - */ void nodeChainIterBackwards(const bNodeTree *ntree, const bNode *node_start, bool (*callback)(bNode *, bNode *, void *), @@ -2158,12 +2111,6 @@ void nodeChainIterBackwards(const bNodeTree *ntree, iter_backwards_ex(ntree, node_start, callback, userdata, recursion_mask); } -/** - * Iterate over all parents of \a node, executing \a callback for each parent - * (which can return false to end iterator) - * - * \note Recursive - */ void nodeParentsIter(bNode *node, bool (*callback)(bNode *, void *), void *userdata) { if (node->parent) { @@ -2176,7 +2123,6 @@ void nodeParentsIter(bNode *node, bool (*callback)(bNode *, void *), void *userd /* ************** Add stuff ********** */ -/* Find the first available, non-duplicate name for a given node */ void nodeUniqueName(bNodeTree *ntree, bNode *node) { BLI_uniquename( @@ -2185,13 +2131,17 @@ void nodeUniqueName(bNodeTree *ntree, bNode *node) bNode *nodeAddNode(const struct bContext *C, bNodeTree *ntree, const char *idname) { - bNode *node = (bNode *)MEM_callocN(sizeof(bNode), "new node"); + bNode *node = MEM_cnew<bNode>("new node"); BLI_addtail(&ntree->nodes, node); BLI_strncpy(node->idname, idname, sizeof(node->idname)); node_set_typeinfo(C, ntree, node, nodeTypeFind(idname)); - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_new(ntree, node); + + if (node->type == GEO_NODE_INPUT_SCENE_TIME) { + DEG_relations_tag_update(CTX_data_main(C)); + } return node; } @@ -2201,9 +2151,8 @@ bNode *nodeAddStaticNode(const struct bContext *C, bNodeTree *ntree, int type) const char *idname = nullptr; NODE_TYPES_BEGIN (ntype) { - /* do an extra poll here, because some int types are used - * for multiple node types, this helps find the desired type - */ + /* Do an extra poll here, because some int types are used + * for multiple node types, this helps find the desired type. */ const char *disabled_hint; if (ntype->type == type && (!ntype->poll || ntype->poll(ntype, ntree, &disabled_hint))) { idname = ntype->idname; @@ -2233,145 +2182,98 @@ static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src, } sock_dst->stack_index = 0; - /* XXX some compositor node (e.g. image, render layers) still store - * some persistent buffer data here, need to clear this to avoid dangling pointers. - */ + /* XXX some compositor nodes (e.g. image, render layers) still store + * some persistent buffer data here, need to clear this to avoid dangling pointers. */ sock_dst->cache = nullptr; } -/* keep socket listorder identical, for copying links */ -/* ntree is the target tree */ -/* unique_name needs to be true. It's only disabled for speed when doing GPUnodetrees. */ -bNode *BKE_node_copy_ex(bNodeTree *ntree, - const bNode *node_src, - const int flag, - const bool unique_name) -{ - bNode *node_dst = (bNode *)MEM_callocN(sizeof(bNode), "dupli node"); - bNodeSocket *sock_dst, *sock_src; - bNodeLink *link_dst, *link_src; - - *node_dst = *node_src; +namespace blender::bke { - /* Reset the declaration of the new node. */ - node_dst->declaration = nullptr; +bNode *node_copy_with_mapping(bNodeTree *dst_tree, + const bNode &node_src, + const int flag, + const bool unique_name, + Map<const bNodeSocket *, bNodeSocket *> &socket_map) +{ + bNode *node_dst = (bNode *)MEM_mallocN(sizeof(bNode), __func__); + *node_dst = node_src; - /* can be called for nodes outside a node tree (e.g. clipboard) */ - if (ntree) { + /* Can be called for nodes outside a node tree (e.g. clipboard). */ + if (dst_tree) { if (unique_name) { - nodeUniqueName(ntree, node_dst); + nodeUniqueName(dst_tree, node_dst); } - - BLI_addtail(&ntree->nodes, node_dst); + BLI_addtail(&dst_tree->nodes, node_dst); } - BLI_duplicatelist(&node_dst->inputs, &node_src->inputs); - for (sock_dst = (bNodeSocket *)node_dst->inputs.first, - sock_src = (bNodeSocket *)node_src->inputs.first; - sock_dst != nullptr; - sock_dst = (bNodeSocket *)sock_dst->next, sock_src = (bNodeSocket *)sock_src->next) { - node_socket_copy(sock_dst, sock_src, flag); + BLI_listbase_clear(&node_dst->inputs); + LISTBASE_FOREACH (const bNodeSocket *, src_socket, &node_src.inputs) { + bNodeSocket *dst_socket = (bNodeSocket *)MEM_dupallocN(src_socket); + node_socket_copy(dst_socket, src_socket, flag); + BLI_addtail(&node_dst->inputs, dst_socket); + socket_map.add_new(src_socket, dst_socket); } - BLI_duplicatelist(&node_dst->outputs, &node_src->outputs); - for (sock_dst = (bNodeSocket *)node_dst->outputs.first, - sock_src = (bNodeSocket *)node_src->outputs.first; - sock_dst != nullptr; - sock_dst = (bNodeSocket *)sock_dst->next, sock_src = (bNodeSocket *)sock_src->next) { - node_socket_copy(sock_dst, sock_src, flag); + BLI_listbase_clear(&node_dst->outputs); + LISTBASE_FOREACH (const bNodeSocket *, src_socket, &node_src.outputs) { + bNodeSocket *dst_socket = (bNodeSocket *)MEM_dupallocN(src_socket); + node_socket_copy(dst_socket, src_socket, flag); + BLI_addtail(&node_dst->outputs, dst_socket); + socket_map.add_new(src_socket, dst_socket); } - if (node_src->prop) { - node_dst->prop = IDP_CopyProperty_ex(node_src->prop, flag); + if (node_src.prop) { + node_dst->prop = IDP_CopyProperty_ex(node_src.prop, flag); } - BLI_duplicatelist(&node_dst->internal_links, &node_src->internal_links); - for (link_dst = (bNodeLink *)node_dst->internal_links.first, - link_src = (bNodeLink *)node_src->internal_links.first; - link_dst != nullptr; - link_dst = (bNodeLink *)link_dst->next, link_src = (bNodeLink *)link_src->next) { - /* This is a bit annoying to do index lookups in a list, but is likely to be faster than - * trying to create a hash-map. At least for usual nodes, which only have so much sockets - * and internal links. */ - const int from_sock_index = BLI_findindex(&node_src->inputs, link_src->fromsock); - const int to_sock_index = BLI_findindex(&node_src->outputs, link_src->tosock); - BLI_assert(from_sock_index != -1); - BLI_assert(to_sock_index != -1); - link_dst->fromnode = node_dst; - link_dst->tonode = node_dst; - link_dst->fromsock = (bNodeSocket *)BLI_findlink(&node_dst->inputs, from_sock_index); - link_dst->tosock = (bNodeSocket *)BLI_findlink(&node_dst->outputs, to_sock_index); + BLI_listbase_clear(&node_dst->internal_links); + LISTBASE_FOREACH (const bNodeLink *, src_link, &node_src.internal_links) { + bNodeLink *dst_link = (bNodeLink *)MEM_dupallocN(src_link); + dst_link->fromnode = node_dst; + dst_link->tonode = node_dst; + dst_link->fromsock = socket_map.lookup(src_link->fromsock); + dst_link->tosock = socket_map.lookup(src_link->tosock); + BLI_addtail(&node_dst->internal_links, dst_link); } if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { id_us_plus(node_dst->id); } - if (node_src->typeinfo->copyfunc) { - node_src->typeinfo->copyfunc(ntree, node_dst, node_src); + if (node_src.typeinfo->copyfunc) { + node_src.typeinfo->copyfunc(dst_tree, node_dst, &node_src); } - node_dst->new_node = nullptr; - /* Only call copy function when a copy is made for the main database, not * for cases like the dependency graph and localization. */ if (node_dst->typeinfo->copyfunc_api && !(flag & LIB_ID_CREATE_NO_MAIN)) { PointerRNA ptr; - RNA_pointer_create((ID *)ntree, &RNA_Node, node_dst, &ptr); + RNA_pointer_create((ID *)dst_tree, &RNA_Node, node_dst, &ptr); - node_dst->typeinfo->copyfunc_api(&ptr, node_src); + node_dst->typeinfo->copyfunc_api(&ptr, &node_src); } - if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + if (dst_tree) { + BKE_ntree_update_tag_node_new(dst_tree, node_dst); } - return node_dst; -} + /* Reset the declaration of the new node. */ + node_dst->declaration = nullptr; + nodeDeclarationEnsure(dst_tree, node_dst); -static void node_set_new_pointers(bNode *node_src, bNode *new_node) -{ - /* Store mapping to the node itself. */ - node_src->new_node = new_node; - /* Store mapping to inputs. */ - bNodeSocket *new_input_sock = (bNodeSocket *)new_node->inputs.first; - bNodeSocket *input_sock_src = (bNodeSocket *)node_src->inputs.first; - while (new_input_sock != nullptr) { - input_sock_src->new_sock = new_input_sock; - new_input_sock = new_input_sock->next; - input_sock_src = input_sock_src->next; - } - /* Store mapping to outputs. */ - bNodeSocket *new_output_sock = (bNodeSocket *)new_node->outputs.first; - bNodeSocket *output_sock_src = (bNodeSocket *)node_src->outputs.first; - while (new_output_sock != nullptr) { - output_sock_src->new_sock = new_output_sock; - new_output_sock = new_output_sock->next; - output_sock_src = output_sock_src->next; - } + return node_dst; } -bNode *BKE_node_copy_store_new_pointers(bNodeTree *ntree, bNode *node_src, const int flag) +bNode *node_copy(bNodeTree *dst_tree, + const bNode &src_node, + const int flag, + const bool unique_name) { - bNode *new_node = BKE_node_copy_ex(ntree, node_src, flag, true); - node_set_new_pointers(node_src, new_node); - return new_node; + Map<const bNodeSocket *, bNodeSocket *> socket_map; + return node_copy_with_mapping(dst_tree, src_node, flag, unique_name, socket_map); } -bNodeTree *ntreeCopyTree_ex_new_pointers(const bNodeTree *ntree, - Main *bmain, - const bool do_id_user) -{ - bNodeTree *new_ntree = ntreeCopyTree_ex(ntree, bmain, do_id_user); - bNode *new_node = (bNode *)new_ntree->nodes.first; - bNode *node_src = (bNode *)ntree->nodes.first; - while (new_node != nullptr) { - node_set_new_pointers(node_src, new_node); - new_node = new_node->next; - node_src = node_src->next; - } - return new_ntree; -} +} // namespace blender::bke static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket) { @@ -2384,18 +2286,17 @@ static int node_count_links(const bNodeTree *ntree, const bNodeSocket *socket) return count; } -/* also used via rna api, so we check for proper input output direction */ bNodeLink *nodeAddLink( bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock) { bNodeLink *link = nullptr; - /* test valid input */ + /* Test valid input. */ BLI_assert(fromnode); BLI_assert(tonode); if (fromsock->in_out == SOCK_OUT && tosock->in_out == SOCK_IN) { - link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "link"); + link = MEM_cnew<bNodeLink>("link"); if (ntree) { BLI_addtail(&ntree->links, link); } @@ -2406,7 +2307,7 @@ bNodeLink *nodeAddLink( } else if (fromsock->in_out == SOCK_IN && tosock->in_out == SOCK_OUT) { /* OK but flip */ - link = (bNodeLink *)MEM_callocN(sizeof(bNodeLink), "link"); + link = MEM_cnew<bNodeLink>("link"); if (ntree) { BLI_addtail(&ntree->links, link); } @@ -2417,7 +2318,7 @@ bNodeLink *nodeAddLink( } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_added(ntree, link); } if (link != nullptr && link->tosock->flag & SOCK_MULTI_INPUT) { @@ -2429,7 +2330,7 @@ bNodeLink *nodeAddLink( void nodeRemLink(bNodeTree *ntree, bNodeLink *link) { - /* can be called for links outside a node tree (e.g. clipboard) */ + /* Can be called for links outside a node tree (e.g. clipboard). */ if (ntree) { BLI_remlink(&ntree->links, link); } @@ -2440,7 +2341,7 @@ void nodeRemLink(bNodeTree *ntree, bNodeLink *link) MEM_freeN(link); if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_removed(ntree); } } @@ -2540,7 +2441,7 @@ void nodeMuteLinkToggle(bNodeTree *ntree, bNodeLink *link) } if (ntree) { - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_mute(ntree, link); } } @@ -2551,8 +2452,6 @@ void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock) nodeRemLink(ntree, link); } } - - ntree->update |= NTREE_UPDATE_LINKS; } bool nodeLinkIsHidden(const bNodeLink *link) @@ -2592,6 +2491,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; @@ -2606,7 +2516,7 @@ void nodeInternalRelink(bNodeTree *ntree, bNode *node) link->flag |= NODE_LINK_MUTED; } - ntree->update |= NTREE_UPDATE_LINKS; + BKE_ntree_update_tag_link_changed(ntree); } else { if (link->tosock->flag & SOCK_MULTI_INPUT) { @@ -2792,21 +2702,24 @@ bNodeTree *ntreeCopyTree(Main *bmain, const bNodeTree *ntree) /* XXX this should be removed eventually ... * Currently BKE functions are modeled closely on previous code, * using BKE_node_preview_init_tree to set up previews for a whole node tree in advance. - * This should be left more to the individual node tree implementations. - */ + * This should be left more to the individual node tree implementations. */ + bool BKE_node_preview_used(const bNode *node) { /* XXX check for closed nodes? */ return (node->typeinfo->flag & NODE_PREVIEW) != 0; } -bNodePreview *BKE_node_preview_verify( - bNodeInstanceHash *previews, bNodeInstanceKey key, int xsize, int ysize, bool create) +bNodePreview *BKE_node_preview_verify(bNodeInstanceHash *previews, + bNodeInstanceKey key, + const int xsize, + const int ysize, + const bool create) { bNodePreview *preview = (bNodePreview *)BKE_node_instance_hash_lookup(previews, key); if (!preview) { if (create) { - preview = (bNodePreview *)MEM_callocN(sizeof(bNodePreview), "node preview"); + preview = MEM_cnew<bNodePreview>("node preview"); BKE_node_instance_hash_insert(previews, key, preview); } else { @@ -2858,9 +2771,8 @@ void BKE_node_preview_free(bNodePreview *preview) static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, bNodeTree *ntree, bNodeInstanceKey parent_key, - int xsize, - int ysize, - bool create_previews) + const int xsize, + const int ysize) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { bNodeInstanceKey key = BKE_node_instance_key(parent_key, ntree, node); @@ -2869,17 +2781,16 @@ static void node_preview_init_tree_recursive(bNodeInstanceHash *previews, node->preview_xsize = xsize; node->preview_ysize = ysize; - BKE_node_preview_verify(previews, key, xsize, ysize, create_previews); + BKE_node_preview_verify(previews, key, xsize, ysize, false); } if (node->type == NODE_GROUP && node->id) { - node_preview_init_tree_recursive( - previews, (bNodeTree *)node->id, key, xsize, ysize, create_previews); + node_preview_init_tree_recursive(previews, (bNodeTree *)node->id, key, xsize, ysize); } } } -void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool create_previews) +void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize) { if (!ntree) { return; @@ -2889,8 +2800,7 @@ void BKE_node_preview_init_tree(bNodeTree *ntree, int xsize, int ysize, bool cre ntree->previews = BKE_node_instance_hash_new("node previews"); } - node_preview_init_tree_recursive( - ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize, create_previews); + node_preview_init_tree_recursive(ntree->previews, ntree, NODE_INSTANCE_KEY_BASE, xsize, ysize); } static void node_preview_tag_used_recursive(bNodeInstanceHash *previews, @@ -2924,18 +2834,6 @@ void BKE_node_preview_remove_unused(bNodeTree *ntree) (bNodeInstanceValueFP)BKE_node_preview_free); } -void BKE_node_preview_free_tree(bNodeTree *ntree) -{ - if (!ntree) { - return; - } - - if (ntree->previews) { - BKE_node_instance_hash_free(ntree->previews, (bNodeInstanceValueFP)BKE_node_preview_free); - ntree->previews = nullptr; - } -} - void BKE_node_preview_clear(bNodePreview *preview) { if (preview && preview->rect) { @@ -2956,40 +2854,6 @@ void BKE_node_preview_clear_tree(bNodeTree *ntree) } } -static void node_preview_sync(bNodePreview *to, bNodePreview *from) -{ - /* sizes should have been initialized by BKE_node_preview_init_tree */ - BLI_assert(to->xsize == from->xsize && to->ysize == from->ysize); - - /* copy over contents of previews */ - if (to->rect && from->rect) { - int xsize = to->xsize; - int ysize = to->ysize; - memcpy(to->rect, from->rect, xsize * ysize * sizeof(char[4])); - } -} - -void BKE_node_preview_sync_tree(bNodeTree *to_ntree, bNodeTree *from_ntree) -{ - bNodeInstanceHash *from_previews = from_ntree->previews; - bNodeInstanceHash *to_previews = to_ntree->previews; - - if (!from_previews || !to_previews) { - return; - } - - bNodeInstanceHashIterator iter; - NODE_INSTANCE_HASH_ITER (iter, from_previews) { - bNodeInstanceKey key = BKE_node_instance_hash_iterator_get_key(&iter); - bNodePreview *from = (bNodePreview *)BKE_node_instance_hash_iterator_get_value(&iter); - bNodePreview *to = (bNodePreview *)BKE_node_instance_hash_lookup(to_previews, key); - - if (from && to) { - node_preview_sync(to, from); - } - } -} - void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, bool remove_old) { if (remove_old || !to_ntree->previews) { @@ -3026,42 +2890,14 @@ void BKE_node_preview_merge_tree(bNodeTree *to_ntree, bNodeTree *from_ntree, boo } } -/* hack warning! this function is only used for shader previews, and - * since it gets called multiple times per pixel for Ztransp we only - * add the color once. Preview gets cleared before it starts render though */ -void BKE_node_preview_set_pixel( - bNodePreview *preview, const float col[4], int x, int y, bool do_manage) -{ - if (preview) { - if (x >= 0 && y >= 0) { - if (x < preview->xsize && y < preview->ysize) { - unsigned char *tar = preview->rect + 4 * ((preview->xsize * y) + x); - - if (do_manage) { - linearrgb_to_srgb_uchar4(tar, col); - } - else { - rgba_float_to_uchar(tar, col); - } - } - // else printf("prv out bound x y %d %d\n", x, y); - } - // else printf("prv out bound x y %d %d\n", x, y); - } -} - /* ************** Free stuff ********** */ -/* goes over entire tree */ void nodeUnlinkNode(bNodeTree *ntree, bNode *node) { LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { ListBase *lb; if (link->fromnode == node) { lb = &node->outputs; - if (link->tonode) { - link->tonode->update |= NODE_UPDATE; - } } else if (link->tonode == node) { lb = &node->inputs; @@ -3103,10 +2939,6 @@ static void node_free_node(bNodeTree *ntree, bNode *node) /* can be called for nodes outside a node tree (e.g. clipboard) */ if (ntree) { - /* remove all references to this node */ - nodeUnlinkNode(ntree, node); - node_unlink_attached(ntree, node); - BLI_remlink(&ntree->nodes, node); if (ntree->typeinfo->free_node_cache) { @@ -3126,12 +2958,12 @@ static void node_free_node(bNodeTree *ntree, bNode *node) LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->outputs) { /* Remember, no ID user refcount management here! */ - node_socket_free(ntree, sock, node, false); + node_socket_free(sock, false); MEM_freeN(sock); } @@ -3143,12 +2975,14 @@ static void node_free_node(bNodeTree *ntree, bNode *node) MEM_freeN(node->prop); } - delete node->declaration; + if (node->typeinfo->declaration_is_dynamic) { + delete node->declaration; + } MEM_freeN(node); if (ntree) { - ntree->update |= NTREE_UPDATE_NODES; + BKE_ntree_update_tag_node_removed(ntree); } } @@ -3156,6 +2990,12 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node) { /* For removing nodes while editing localized node trees. */ BLI_assert((ntree->id.tag & LIB_TAG_LOCALIZED) != 0); + + /* These two lines assume the caller might want to free a single node and maintain + * a valid state in the node tree. */ + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + node_free_node(ntree, node); } @@ -3200,6 +3040,9 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user) } } + nodeUnlinkNode(ntree, node); + node_unlink_attached(ntree, node); + /* Free node itself. */ node_free_node(ntree, node); } @@ -3225,8 +3068,7 @@ static void free_localized_node_groups(bNodeTree *ntree) /* Only localized node trees store a copy for each node group tree. * Each node group tree in a localized node tree can be freed, * since it is a localized copy itself (no risk of accessing free'd - * data in main, see T37939). - */ + * data in main, see T37939). */ if (!(ntree->id.tag & LIB_TAG_LOCALIZED)) { return; } @@ -3240,8 +3082,6 @@ static void free_localized_node_groups(bNodeTree *ntree) } } -/* Free (or release) any data used by this nodetree. Does not free the - * nodetree itself and does no ID user counting. */ void ntreeFreeTree(bNodeTree *ntree) { ntree_free_data(&ntree->id); @@ -3345,12 +3185,6 @@ void ntreeSetOutput(bNodeTree *ntree) * might be different for editor or for "real" use... */ } -/** - * Get address of potential node-tree pointer of given ID. - * - * \warning Using this function directly is potentially dangerous, if you don't know or are not - * sure, please use `ntreeFromID()` instead. - */ bNodeTree **BKE_ntree_ptr_from_id(ID *id) { switch (GS(id->name)) { @@ -3373,33 +3207,12 @@ bNodeTree **BKE_ntree_ptr_from_id(ID *id) } } -/* Returns the private NodeTree object of the datablock, if it has one. */ bNodeTree *ntreeFromID(ID *id) { bNodeTree **nodetree = BKE_ntree_ptr_from_id(id); return (nodetree != nullptr) ? *nodetree : nullptr; } -bool ntreeNodeExists(const bNodeTree *ntree, const bNode *testnode) -{ - LISTBASE_FOREACH (const bNode *, node, &ntree->nodes) { - if (node == testnode) { - return true; - } - } - return false; -} - -bool ntreeOutputExists(const bNode *node, const bNodeSocket *testsock) -{ - LISTBASE_FOREACH (const bNodeSocket *, sock, &node->outputs) { - if (sock == testsock) { - return true; - } - } - return false; -} - void ntreeNodeFlagSet(const bNodeTree *ntree, const int flag, const bool enable) { LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { @@ -3412,59 +3225,43 @@ void ntreeNodeFlagSet(const bNodeTree *ntree, const int flag, const bool enable) } } -/* returns localized tree for execution in threads */ bNodeTree *ntreeLocalize(bNodeTree *ntree) { - if (ntree) { - /* Make full copy outside of Main database. - * 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)); - - ltree->id.tag |= LIB_TAG_LOCALIZED; + if (ntree == nullptr) { + return nullptr; + } - LISTBASE_FOREACH (bNode *, node, <ree->nodes) { - if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) { - node->id = (ID *)ntreeLocalize((bNodeTree *)node->id); - } - } + /* Make full copy outside of Main database. + * 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)); - /* ensures only a single output node is enabled */ - ntreeSetOutput(ntree); + ltree->id.tag |= LIB_TAG_LOCALIZED; - bNode *node_src = (bNode *)ntree->nodes.first; - bNode *node_local = (bNode *)ltree->nodes.first; - while (node_src != nullptr) { - node_local->original = node_src; - node_src = node_src->next; - node_local = node_local->next; + LISTBASE_FOREACH (bNode *, node, <ree->nodes) { + if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP) && node->id) { + node->id = (ID *)ntreeLocalize((bNodeTree *)node->id); } + } - if (ntree->typeinfo->localize) { - ntree->typeinfo->localize(ltree, ntree); - } + /* Ensures only a single output node is enabled. */ + ntreeSetOutput(ntree); - return ltree; + bNode *node_src = (bNode *)ntree->nodes.first; + bNode *node_local = (bNode *)ltree->nodes.first; + while (node_src != nullptr) { + node_local->original = node_src; + node_src = node_src->next; + node_local = node_local->next; } - return nullptr; -} - -/* sync local composite with real tree */ -/* local tree is supposed to be running, be careful moving previews! */ -/* is called by jobs manager, outside threads, so it doesn't happen during draw */ -void ntreeLocalSync(bNodeTree *localtree, bNodeTree *ntree) -{ - if (localtree && ntree) { - if (ntree->typeinfo->local_sync) { - ntree->typeinfo->local_sync(localtree, ntree); - } + if (ntree->typeinfo->localize) { + ntree->typeinfo->localize(ltree, ntree); } + + return ltree; } -/* merge local tree results back, and free local tree */ -/* we have to assume the editor already changed completely */ void ntreeLocalMerge(Main *bmain, bNodeTree *localtree, bNodeTree *ntree) { if (ntree && localtree) { @@ -3489,7 +3286,7 @@ static bNodeSocket *make_socket_interface(bNodeTree *ntree, return nullptr; } - bNodeSocket *sock = (bNodeSocket *)MEM_callocN(sizeof(bNodeSocket), "socket template"); + bNodeSocket *sock = MEM_cnew<bNodeSocket>("socket template"); BLI_strncpy(sock->idname, stype->idname, sizeof(sock->idname)); node_socket_set_typeinfo(ntree, sock, stype); sock->in_out = in_out; @@ -3535,12 +3332,11 @@ bNodeSocket *ntreeAddSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_addtail(&ntree->inputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_addtail(&ntree->outputs, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3553,12 +3349,11 @@ bNodeSocket *ntreeInsertSocketInterface(bNodeTree *ntree, bNodeSocket *iosock = make_socket_interface(ntree, in_out, idname, name); if (in_out == SOCK_IN) { BLI_insertlinkbefore(&ntree->inputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_IN; } else if (in_out == SOCK_OUT) { BLI_insertlinkbefore(&ntree->outputs, next_sock, iosock); - ntree->update |= NTREE_UPDATE_GROUP_OUT; } + BKE_ntree_update_tag_interface(ntree); return iosock; } @@ -3604,7 +3399,7 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock) node_socket_interface_free(ntree, sock, true); MEM_freeN(sock); - ntree->update |= NTREE_UPDATE_GROUP; + BKE_ntree_update_tag_interface(ntree); } /* generates a valid RNA identifier from the node tree name */ @@ -3742,11 +3537,6 @@ bNode *ntreeFindType(const bNodeTree *ntree, int type) return nullptr; } -bool ntreeHasType(const bNodeTree *ntree, int type) -{ - return ntreeFindType(ntree, type) != nullptr; -} - bool ntreeHasTree(const bNodeTree *ntree, const bNodeTree *lookup) { if (ntree == lookup) { @@ -3800,95 +3590,6 @@ bNode *nodeGetActive(bNodeTree *ntree) return nullptr; } -static bNode *node_get_active_id_recursive(bNodeInstanceKey active_key, - bNodeInstanceKey parent_key, - bNodeTree *ntree, - short idtype) -{ - if (parent_key.value == active_key.value || active_key.value == 0) { - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id && GS(node->id->name) == idtype) { - if (node->flag & NODE_ACTIVE_ID) { - return node; - } - } - } - } - else { - /* no node with active ID in this tree, look inside groups */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == NODE_GROUP) { - bNodeTree *group = (bNodeTree *)node->id; - if (group) { - bNodeInstanceKey group_key = BKE_node_instance_key(parent_key, ntree, node); - bNode *tnode = node_get_active_id_recursive(active_key, group_key, group, idtype); - if (tnode) { - return tnode; - } - } - } - } - } - return nullptr; -} - -/* two active flags, ID nodes have special flag for buttons display */ -bNode *nodeGetActiveID(bNodeTree *ntree, short idtype) -{ - if (ntree) { - return node_get_active_id_recursive( - ntree->active_viewer_key, NODE_INSTANCE_KEY_BASE, ntree, idtype); - } - return nullptr; -} - -bool nodeSetActiveID(bNodeTree *ntree, short idtype, ID *id) -{ - bool ok = false; - - if (ntree == nullptr) { - return ok; - } - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id && GS(node->id->name) == idtype) { - if (id && ok == false && node->id == id) { - node->flag |= NODE_ACTIVE_ID; - ok = true; - } - else { - node->flag &= ~NODE_ACTIVE_ID; - } - } - } - - /* update all groups linked from here - * if active ID node has been found already, - * just pass null so other matching nodes are deactivated. - */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->type == NODE_GROUP) { - ok |= nodeSetActiveID((bNodeTree *)node->id, idtype, (ok == false ? id : nullptr)); - } - } - - return ok; -} - -/* two active flags, ID nodes have special flag for buttons display */ -void nodeClearActiveID(bNodeTree *ntree, short idtype) -{ - if (ntree == nullptr) { - return; - } - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id && GS(node->id->name) == idtype) { - node->flag &= ~NODE_ACTIVE_ID; - } - } -} - void nodeSetSelected(bNode *node, bool select) { if (select) { @@ -3914,22 +3615,16 @@ void nodeClearActive(bNodeTree *ntree) } LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - node->flag &= ~(NODE_ACTIVE | NODE_ACTIVE_ID); + node->flag &= ~NODE_ACTIVE; } } -/* two active flags, ID nodes have special flag for buttons display */ void nodeSetActive(bNodeTree *ntree, bNode *node) { /* make sure only one node is active, and only one per ID type */ LISTBASE_FOREACH (bNode *, tnode, &ntree->nodes) { tnode->flag &= ~NODE_ACTIVE; - if (node->id && tnode->id) { - if (GS(node->id->name) == GS(tnode->id->name)) { - tnode->flag &= ~NODE_ACTIVE_ID; - } - } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { tnode->flag &= ~NODE_ACTIVE_TEXTURE; @@ -3937,9 +3632,6 @@ void nodeSetActive(bNodeTree *ntree, bNode *node) } node->flag |= NODE_ACTIVE; - if (node->id) { - node->flag |= NODE_ACTIVE_ID; - } if ((node->typeinfo->nclass == NODE_CLASS_TEXTURE) || (node->typeinfo->type == GEO_NODE_LEGACY_ATTRIBUTE_SAMPLE_TEXTURE)) { node->flag |= NODE_ACTIVE_TEXTURE; @@ -3951,8 +3643,13 @@ int nodeSocketIsHidden(const bNodeSocket *sock) return ((sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) != 0); } -void nodeSetSocketAvailability(bNodeSocket *sock, bool is_available) +void nodeSetSocketAvailability(bNodeTree *ntree, bNodeSocket *sock, bool is_available) { + const bool was_available = (sock->flag & SOCK_UNAVAIL) == 0; + if (is_available != was_available) { + BKE_ntree_update_tag_socket_availability(ntree, sock); + } + if (is_available) { sock->flag &= ~SOCK_UNAVAIL; } @@ -3975,22 +3672,51 @@ int nodeSocketLinkLimit(const bNodeSocket *sock) return sock->limit; } -/** - * If the node implements a `declare` function, this function makes sure that `node->declaration` - * is up to date. - */ -void nodeDeclarationEnsure(bNodeTree *UNUSED(ntree), bNode *node) +static void update_socket_declarations(ListBase *sockets, + Span<blender::nodes::SocketDeclarationPtr> declarations) { - if (node->typeinfo->declare == nullptr) { - return; + int index; + LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, sockets, index) { + const SocketDeclaration &socket_decl = *declarations[index]; + socket->declaration = &socket_decl; } +} + +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()); +} + +bool nodeDeclarationEnsureOnOutdatedNode(bNodeTree *UNUSED(ntree), bNode *node) +{ if (node->declaration != nullptr) { - return; + 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; +} - node->declaration = new blender::nodes::NodeDeclaration(); - blender::nodes::NodeDeclarationBuilder builder{*node->declaration}; - node->typeinfo->declare(builder); +bool nodeDeclarationEnsure(bNodeTree *ntree, bNode *node) +{ + if (nodeDeclarationEnsureOnOutdatedNode(ntree, node)) { + nodeSocketDeclarationsUpdate(node); + return true; + } + return false; } /* ************** Node Clipboard *********** */ @@ -4031,7 +3757,7 @@ void BKE_node_clipboard_init(const struct bNodeTree *ntree) node_clipboard.type = ntree->type; } -void BKE_node_clipboard_clear(void) +void BKE_node_clipboard_clear() { LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &node_clipboard.links) { nodeRemLink(nullptr, link); @@ -4048,8 +3774,7 @@ void BKE_node_clipboard_clear(void) #endif } -/* return false when one or more ID's are lost */ -bool BKE_node_clipboard_validate(void) +bool BKE_node_clipboard_validate() { bool ok = true; @@ -4127,22 +3852,22 @@ void BKE_node_clipboard_add_link(bNodeLink *link) BLI_addtail(&node_clipboard.links, link); } -const ListBase *BKE_node_clipboard_get_nodes(void) +const ListBase *BKE_node_clipboard_get_nodes() { return &node_clipboard.nodes; } -const ListBase *BKE_node_clipboard_get_links(void) +const ListBase *BKE_node_clipboard_get_links() { return &node_clipboard.links; } -int BKE_node_clipboard_get_type(void) +int BKE_node_clipboard_get_type() { return node_clipboard.type; } -void BKE_node_clipboard_free(void) +void BKE_node_clipboard_free() { BKE_node_clipboard_validate(); BKE_node_clipboard_clear(); @@ -4150,7 +3875,6 @@ void BKE_node_clipboard_free(void) /* Node Instance Hash */ -/* magic number for initial hash key */ const bNodeInstanceKey NODE_INSTANCE_KEY_BASE = {5381}; const bNodeInstanceKey NODE_INSTANCE_KEY_NONE = {0}; @@ -4377,7 +4101,7 @@ void ntreeGetDependencyList(struct bNodeTree *ntree, struct bNode ***r_deplist, } /* only updates node->level for detecting cycles links */ -static void ntree_update_node_level(bNodeTree *ntree) +void ntreeUpdateNodeLevels(bNodeTree *ntree) { /* first clear tag */ LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { @@ -4392,763 +4116,48 @@ static void ntree_update_node_level(bNodeTree *ntree) } } -void ntreeTagUsedSockets(bNodeTree *ntree) -{ - /* first clear data */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - sock->flag &= ~SOCK_IN_USE; - } - LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) { - sock->flag &= ~SOCK_IN_USE; - } - } - - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->fromsock->flag |= SOCK_IN_USE; - if (!(link->flag & NODE_LINK_MUTED)) { - link->tosock->flag |= SOCK_IN_USE; - } - } -} - -static void ntree_update_link_pointers(bNodeTree *ntree) -{ - /* first clear data */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { - sock->link = nullptr; - } - } - - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->tosock->link = link; - } - - ntreeTagUsedSockets(ntree); -} - -static void ntree_validate_links(bNodeTree *ntree) -{ - LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) { - link->flag |= NODE_LINK_VALID; - if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { - link->flag &= ~NODE_LINK_VALID; - } - else if (ntree->typeinfo->validate_link) { - if (!ntree->typeinfo->validate_link(ntree, link)) { - link->flag &= ~NODE_LINK_VALID; - } - } - } -} - void ntreeUpdateAllNew(Main *main) { + Vector<bNodeTree *> new_ntrees; + /* Update all new node trees on file read or append, to add/remove sockets * in groups nodes if the group changed, and handle any update flags that * might have been set in file reading or versioning. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { if (owner_id->tag & LIB_TAG_NEW) { - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - } - - ntreeUpdateTree(nullptr, ntree); + BKE_ntree_update_tag_all(ntree); } } FOREACH_NODETREE_END; + BKE_ntree_update_main(main, nullptr); } -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(); -} - -/** - * 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 (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 is currently a single value. It could become a field though. */ - bool is_single = true; - /* This socket is required to be a single value. It must not be a field. */ - bool requires_single = false; - /* This socket starts a new field. */ - bool is_field_source = 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->logically_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 Vector<const NodeRef *> sorted_nodes = tree.toposort( - NodeTreeRef::ToposortDirection::RightToLeft); - - for (const NodeRef *node : 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; - 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; - } - } - } -} - -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) -{ - Vector<const NodeRef *> sorted_nodes = tree.toposort( - NodeTreeRef::ToposortDirection::LeftToRight); - - for (const NodeRef *node : 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.requires_single) { - state.is_single = true; - continue; - } - state.is_single = true; - if (input_socket->logically_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->logically_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 (!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; - - for (const InputSocketRef *socket : tree.input_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - if (state.requires_single) { - bsocket->display_shape = requires_data_shape; - } - else if (state.is_single) { - bsocket->display_shape = data_but_can_be_field_shape; - } - else { - bsocket->display_shape = is_field_shape; - } - } - for (const OutputSocketRef *socket : tree.output_sockets()) { - bNodeSocket *bsocket = socket->bsocket(); - const SocketFieldState &state = field_state_by_socket_id[socket->id()]; - if (state.requires_single) { - bsocket->display_shape = requires_data_shape; - } - else if (state.is_single) { - bsocket->display_shape = data_but_can_be_field_shape; - } - else { - bsocket->display_shape = is_field_shape; - } - } -} - -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) +void ntreeUpdateAllUsers(Main *main, ID *id) { if (id == nullptr) { return; } + bool need_update = false; + /* Update all users of ngroup, to add/remove sockets as needed. */ FOREACH_NODETREE_BEGIN (main, ntree, owner_id) { - bool need_update = false; - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { if (node->id == id) { - if (node->typeinfo->group_update_func) { - node->typeinfo->group_update_func(ntree, node); - } - + BKE_ntree_update_tag_node_property(ntree, node); need_update = true; } } - - if (need_update) { - ntree->update |= tree_update_flag; - ntreeUpdateTree(tree_update_flag ? main : nullptr, ntree); - } } FOREACH_NODETREE_END; - - if (GS(id->name) == ID_NT) { - bNodeTree *ngroup = (bNodeTree *)id; - if (ngroup->type == NTREE_GEOMETRY && (ngroup->update & NTREE_UPDATE_GROUP)) { - LISTBASE_FOREACH (Object *, object, &main->objects) { - LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { - if (md->type == eModifierType_Nodes) { - NodesModifierData *nmd = (NodesModifierData *)md; - if (nmd->node_group == ngroup) { - MOD_nodes_update_interface(object, nmd); - } - } - } - } - } - } -} - -void ntreeUpdateTree(Main *bmain, bNodeTree *ntree) -{ - if (!ntree) { - return; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* set the bNodeSocket->link pointers */ - ntree_update_link_pointers(ntree); - } - - /* update individual nodes */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - /* node tree update tags override individual node update flags */ - if ((node->update & NODE_UPDATE) || (ntree->update & NTREE_UPDATE)) { - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - } - } - - /* generic tree update callback */ - if (ntree->typeinfo->update) { - ntree->typeinfo->update(ntree); - } - /* XXX this should be moved into the tree type update callback for tree supporting node groups. - * Currently the node tree interface is still a generic feature of the base NodeTree type. - */ - if (ntree->update & NTREE_UPDATE_GROUP) { - 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, tree_user_update_flag); - } - - if (ntree->update & (NTREE_UPDATE_LINKS | NTREE_UPDATE_NODES)) { - /* node updates can change sockets or links, repeat link pointer update afterward */ - ntree_update_link_pointers(ntree); - - /* update the node level from link dependencies */ - ntree_update_node_level(ntree); - - /* check link validity */ - ntree_validate_links(ntree); - } - - /* clear update flags */ - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - node->update = 0; - } - ntree->update = 0; - - ntree->is_updating = false; -} - -void nodeUpdate(bNodeTree *ntree, bNode *node) -{ - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return; - } - ntree->is_updating = true; - - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - - nodeUpdateInternalLinks(ntree, node); - - /* clear update flag */ - node->update = 0; - - ntree->is_updating = false; -} - -bool nodeUpdateID(bNodeTree *ntree, ID *id) -{ - bool changed = false; - - if (ELEM(nullptr, id, ntree)) { - return changed; - } - - /* Avoid re-entrant updates, can be caused by RNA update callbacks. */ - if (ntree->is_updating) { - return changed; - } - ntree->is_updating = true; - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - if (node->id == id) { - changed = true; - node->update |= NODE_UPDATE_ID; - if (node->typeinfo->updatefunc) { - node->typeinfo->updatefunc(ntree, node); - } - /* clear update flag */ - node->update = 0; - } - } - - LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { - nodeUpdateInternalLinks(ntree, node); - } - - ntree->is_updating = false; - return changed; -} - -void nodeUpdateInternalLinks(bNodeTree *ntree, bNode *node) -{ - BLI_freelistN(&node->internal_links); - - if (node->typeinfo && node->typeinfo->update_internal_links) { - node->typeinfo->update_internal_links(ntree, node); + if (need_update) { + BKE_ntree_update_main(main, nullptr); } } /* ************* node type access ********** */ -void nodeLabel(bNodeTree *ntree, bNode *node, char *label, int maxlen) +void nodeLabel(const bNodeTree *ntree, const bNode *node, char *label, int maxlen) { label[0] = '\0'; @@ -5170,7 +4179,6 @@ void nodeLabel(bNodeTree *ntree, bNode *node, char *label, int maxlen) } } -/* Get node socket label if it is set */ const char *nodeSocketLabel(const bNodeSocket *sock) { return (sock->label[0] != '\0') ? sock->label : sock->name; @@ -5199,8 +4207,7 @@ static bool node_poll_instance_default(bNode *node, bNodeTree *ntree, const char return node->typeinfo->poll(node->typeinfo, ntree, disabled_hint); } -/* NOLINTNEXTLINE: readability-function-size */ -void node_type_base(bNodeType *ntype, int type, const char *name, short nclass, short flag) +void node_type_base(bNodeType *ntype, int type, const char *name, short nclass) { /* Use static type info header to map static int type to identifier string and RNA struct type. * Associate the RNA struct type with the bNodeType. @@ -5227,7 +4234,6 @@ void node_type_base(bNodeType *ntype, int type, const char *name, short nclass, ntype->type = type; BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name)); ntype->nclass = nclass; - ntype->flag = flag; node_type_base_defaults(ntype); @@ -5235,14 +4241,12 @@ void node_type_base(bNodeType *ntype, int type, const char *name, short nclass, ntype->poll_instance = node_poll_instance_default; } -void node_type_base_custom( - bNodeType *ntype, const char *idname, const char *name, short nclass, short flag) +void node_type_base_custom(bNodeType *ntype, const char *idname, const char *name, short nclass) { BLI_strncpy(ntype->idname, idname, sizeof(ntype->idname)); ntype->type = NODE_CUSTOM; BLI_strncpy(ntype->ui_name, name, sizeof(ntype->ui_name)); ntype->nclass = nclass; - ntype->flag = flag; node_type_base_defaults(ntype); } @@ -5352,10 +4356,6 @@ void node_type_size_preset(struct bNodeType *ntype, eNodeSizePreset size) } } -/** - * \warning Nodes defining a storage type _must_ allocate this for new nodes. - * Otherwise nodes will reload as undefined (T46619). - */ void node_type_storage(bNodeType *ntype, const char *storagename, void (*freefunc)(struct bNode *node), @@ -5373,13 +4373,6 @@ void node_type_storage(bNodeType *ntype, ntype->freefunc = freefunc; } -void node_type_label( - struct bNodeType *ntype, - void (*labelfunc)(struct bNodeTree *ntree, struct bNode *node, char *label, int maxlen)) -{ - ntype->labelfunc = labelfunc; -} - void node_type_update(struct bNodeType *ntype, void (*updatefunc)(struct bNodeTree *ntree, struct bNode *node)) { @@ -5407,12 +4400,6 @@ void node_type_gpu(struct bNodeType *ntype, NodeGPUExecFunction gpu_fn) ntype->gpu_fn = gpu_fn; } -void node_type_internal_links(bNodeType *ntype, - void (*update_internal_links)(bNodeTree *, bNode *)) -{ - ntype->update_internal_links = update_internal_links; -} - /* callbacks for undefined types */ static bool node_undefined_poll(bNodeType *UNUSED(ntype), @@ -5430,11 +4417,12 @@ static void register_undefined_types() * 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")); - node_type_base_custom(&NodeTypeUndefined, "NodeUndefined", "Undefined", 0, 0); + node_type_base_custom(&NodeTypeUndefined, "NodeUndefined", "Undefined", 0); NodeTypeUndefined.poll = node_undefined_poll; BLI_strncpy(NodeSocketTypeUndefined.idname, @@ -5459,6 +4447,7 @@ static void registerCompositNodes() register_node_type_cmp_value(); register_node_type_cmp_rgb(); register_node_type_cmp_curve_time(); + register_node_type_cmp_scene_time(); register_node_type_cmp_movieclip(); register_node_type_cmp_composite(); @@ -5499,6 +4488,7 @@ static void registerCompositNodes() register_node_type_cmp_denoise(); register_node_type_cmp_antialiasing(); + register_node_type_cmp_convert_color_space(); register_node_type_cmp_valtorgb(); register_node_type_cmp_rgbtobw(); register_node_type_cmp_setalpha(); @@ -5574,6 +4564,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(); @@ -5677,6 +4668,7 @@ static void registerTextureNodes() register_node_type_sh_tangent(); register_node_type_sh_normal_map(); register_node_type_sh_hair_info(); + register_node_type_sh_point_info(); register_node_type_sh_volume_info(); register_node_type_tex_checker(); @@ -5709,10 +4701,25 @@ static void registerGeometryNodes() 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_curve_reverse(); + register_node_type_geo_legacy_subdivision_surface(); + register_node_type_geo_legacy_volume_to_mesh(); + register_node_type_geo_accumulate_field(); register_node_type_geo_align_rotation_to_vector(); register_node_type_geo_attribute_capture(); register_node_type_geo_attribute_clamp(); @@ -5721,6 +4728,7 @@ static void registerGeometryNodes() register_node_type_geo_attribute_compare(); register_node_type_geo_attribute_convert(); register_node_type_geo_attribute_curve_map(); + register_node_type_geo_attribute_domain_size(); register_node_type_geo_attribute_fill(); register_node_type_geo_attribute_map_range(); register_node_type_geo_attribute_math(); @@ -5728,18 +4736,18 @@ static void registerGeometryNodes() register_node_type_geo_attribute_remove(); register_node_type_geo_attribute_separate_xyz(); register_node_type_geo_attribute_statistic(); - register_node_type_geo_attribute_transfer(); register_node_type_geo_attribute_vector_math(); register_node_type_geo_attribute_vector_rotate(); 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_endpoints(); + 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_parameter(); + register_node_type_geo_curve_primitive_arc(); register_node_type_geo_curve_primitive_bezier_segment(); register_node_type_geo_curve_primitive_circle(); register_node_type_geo_curve_primitive_line(); @@ -5751,6 +4759,7 @@ static void registerGeometryNodes() 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_parameter(); register_node_type_geo_curve_spline_type(); register_node_type_geo_curve_subdivide(); register_node_type_geo_curve_to_mesh(); @@ -5758,18 +4767,42 @@ static void registerGeometryNodes() register_node_type_geo_curve_trim(); register_node_type_geo_delete_geometry(); register_node_type_geo_distribute_points_on_faces(); + register_node_type_geo_dual_mesh(); register_node_type_geo_edge_split(); + register_node_type_geo_extrude_mesh(); + register_node_type_geo_field_at_index(); + register_node_type_geo_flip_faces(); + register_node_type_geo_geometry_to_instance(); + 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_mesh_edge_angle(); + register_node_type_geo_input_mesh_edge_neighbors(); + register_node_type_geo_input_mesh_edge_vertices(); + register_node_type_geo_input_mesh_face_area(); + register_node_type_geo_input_mesh_face_neighbors(); + register_node_type_geo_input_mesh_island(); + register_node_type_geo_input_mesh_vertex_neighbors(); register_node_type_geo_input_normal(); register_node_type_geo_input_position(); + register_node_type_geo_input_radius(); + register_node_type_geo_input_scene_time(); + 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_merge_by_distance(); register_node_type_geo_mesh_primitive_circle(); register_node_type_geo_mesh_primitive_cone(); register_node_type_geo_mesh_primitive_cube(); @@ -5793,15 +4826,30 @@ static void registerGeometryNodes() 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_handle_type(); + register_node_type_geo_scale_elements(); + register_node_type_geo_scale_instances(); register_node_type_geo_separate_components(); + 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(); @@ -5811,19 +4859,25 @@ 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_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_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_string_substring(); register_node_type_fn_value_to_string(); } -void BKE_node_system_init(void) +void BKE_node_system_init() { nodetreetypes_hash = BLI_ghash_str_new("nodetreetypes_hash gh"); nodetypes_hash = BLI_ghash_str_new("nodetypes_hash gh"); @@ -5850,7 +4904,7 @@ void BKE_node_system_init(void) registerFunctionNodes(); } -void BKE_node_system_exit(void) +void BKE_node_system_exit() { if (nodetypes_hash) { NODE_TYPES_BEGIN (nt) { diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc new file mode 100644 index 00000000000..0555707b64c --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -0,0 +1,1670 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BLI_map.hh" +#include "BLI_multi_value_map.hh" +#include "BLI_noise.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector_set.hh" + +#include "DNA_anim_types.h" +#include "DNA_modifier_types.h" +#include "DNA_node_types.h" + +#include "BKE_anim_data.h" +#include "BKE_main.h" +#include "BKE_node.h" +#include "BKE_node_tree_update.h" + +#include "MOD_nodes.h" + +#include "NOD_node_declaration.hh" +#include "NOD_node_tree_ref.hh" +#include "NOD_texture.h" + +#include "DEG_depsgraph_query.h" + +using namespace blender::nodes; + +/** + * These flags are used by the `changed_flag` field in #bNodeTree, #bNode and #bNodeSocket. + * This enum is not part of the public api. It should be used through the `BKE_ntree_update_tag_*` + * api. + */ +enum eNodeTreeChangedFlag { + NTREE_CHANGED_NOTHING = 0, + NTREE_CHANGED_ANY = (1 << 1), + NTREE_CHANGED_NODE_PROPERTY = (1 << 2), + NTREE_CHANGED_NODE_OUTPUT = (1 << 3), + NTREE_CHANGED_INTERFACE = (1 << 4), + NTREE_CHANGED_LINK = (1 << 5), + NTREE_CHANGED_REMOVED_NODE = (1 << 6), + NTREE_CHANGED_REMOVED_SOCKET = (1 << 7), + NTREE_CHANGED_SOCKET_PROPERTY = (1 << 8), + NTREE_CHANGED_INTERNAL_LINK = (1 << 9), + NTREE_CHANGED_ALL = -1, +}; + +static void add_tree_tag(bNodeTree *ntree, const eNodeTreeChangedFlag flag) +{ + ntree->changed_flag |= flag; +} + +static void add_node_tag(bNodeTree *ntree, bNode *node, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + node->changed_flag |= flag; +} + +static void add_socket_tag(bNodeTree *ntree, bNodeSocket *socket, const eNodeTreeChangedFlag flag) +{ + add_tree_tag(ntree, flag); + socket->changed_flag |= flag; +} + +namespace blender::bke { + +namespace 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 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) { + /* This shouldn't happen because referenced node groups should always be updated first. */ + BLI_assert_unreachable(); + } + 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 (!origin_input_socket->is_available()) { + continue; + } + 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()) { + if (target_socket->is_available()) { + 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 (!input_socket->is_available()) { + continue; + } + 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(const NodeTreeRef &tree) +{ + bNodeTree &btree = *tree.btree(); + + /* 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()); + + /* 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 node_field_inferencing + +/** + * Common datatype priorities, works for compositor, shader and texture nodes alike + * defines priority of datatype connection based on output type (to): + * `< 0`: never connect these types. + * `>= 0`: priority of connection (higher values chosen first). + */ +static int get_internal_link_type_priority(const bNodeSocketType *from, const bNodeSocketType *to) +{ + switch (to->type) { + case SOCK_RGBA: + switch (from->type) { + case SOCK_RGBA: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_VECTOR: + switch (from->type) { + case SOCK_VECTOR: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_INT: + return 2; + case SOCK_BOOLEAN: + return 1; + } + return -1; + case SOCK_FLOAT: + switch (from->type) { + case SOCK_FLOAT: + return 5; + case SOCK_INT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_INT: + switch (from->type) { + case SOCK_INT: + return 5; + case SOCK_FLOAT: + return 4; + case SOCK_BOOLEAN: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + case SOCK_BOOLEAN: + switch (from->type) { + case SOCK_BOOLEAN: + return 5; + case SOCK_INT: + return 4; + case SOCK_FLOAT: + return 3; + case SOCK_RGBA: + return 2; + case SOCK_VECTOR: + return 1; + } + return -1; + } + + /* The rest of the socket types only allow an internal link if both the input and output socket + * have the same type. If the sockets are custom, we check the idname instead. */ + if (to->type == from->type && (to->type != SOCK_CUSTOM || STREQ(to->idname, from->idname))) { + return 1; + } + + return -1; +} + +using TreeNodePair = std::pair<bNodeTree *, bNode *>; +using ObjectModifierPair = std::pair<Object *, ModifierData *>; +using NodeSocketPair = std::pair<bNode *, bNodeSocket *>; + +/** + * Cache common data about node trees from the #Main database that is expensive to retrieve on + * demand every time. + */ +struct NodeTreeRelations { + private: + Main *bmain_; + std::optional<Vector<bNodeTree *>> all_trees_; + std::optional<Map<bNodeTree *, ID *>> owner_ids_; + std::optional<MultiValueMap<bNodeTree *, TreeNodePair>> group_node_users_; + std::optional<MultiValueMap<bNodeTree *, ObjectModifierPair>> modifiers_users_; + + public: + NodeTreeRelations(Main *bmain) : bmain_(bmain) + { + } + + void ensure_all_trees() + { + if (all_trees_.has_value()) { + return; + } + all_trees_.emplace(); + owner_ids_.emplace(); + if (bmain_ == nullptr) { + return; + } + + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + all_trees_->append(ntree); + if (&ntree->id != id) { + owner_ids_->add_new(ntree, id); + } + } + FOREACH_NODETREE_END; + } + + void ensure_owner_ids() + { + this->ensure_all_trees(); + } + + void ensure_group_node_users() + { + if (group_node_users_.has_value()) { + return; + } + group_node_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + this->ensure_all_trees(); + + for (bNodeTree *ntree : *all_trees_) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == nullptr) { + continue; + } + ID *id = node->id; + if (GS(id->name) == ID_NT) { + bNodeTree *group = (bNodeTree *)id; + group_node_users_->add(group, {ntree, node}); + } + } + } + } + + void ensure_modifier_users() + { + if (modifiers_users_.has_value()) { + return; + } + modifiers_users_.emplace(); + if (bmain_ == nullptr) { + return; + } + + LISTBASE_FOREACH (Object *, object, &bmain_->objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = (NodesModifierData *)md; + if (nmd->node_group != nullptr) { + modifiers_users_->add(nmd->node_group, {object, md}); + } + } + } + } + } + + Span<ObjectModifierPair> get_modifier_users(bNodeTree *ntree) + { + BLI_assert(modifiers_users_.has_value()); + return modifiers_users_->lookup(ntree); + } + + Span<TreeNodePair> get_group_node_users(bNodeTree *ntree) + { + BLI_assert(group_node_users_.has_value()); + return group_node_users_->lookup(ntree); + } + + ID *get_owner_id(bNodeTree *ntree) + { + BLI_assert(owner_ids_.has_value()); + return owner_ids_->lookup_default(ntree, &ntree->id); + } +}; + +struct TreeUpdateResult { + bool interface_changed = false; + bool output_changed = false; +}; + +class NodeTreeMainUpdater { + private: + Main *bmain_; + NodeTreeUpdateExtraParams *params_; + Map<bNodeTree *, TreeUpdateResult> update_result_by_tree_; + NodeTreeRelations relations_; + + public: + NodeTreeMainUpdater(Main *bmain, NodeTreeUpdateExtraParams *params) + : bmain_(bmain), params_(params), relations_(bmain) + { + } + + void update() + { + Vector<bNodeTree *> changed_ntrees; + FOREACH_NODETREE_BEGIN (bmain_, ntree, id) { + if (ntree->changed_flag != NTREE_CHANGED_NOTHING) { + changed_ntrees.append(ntree); + } + } + FOREACH_NODETREE_END; + this->update_rooted(changed_ntrees); + } + + void update_rooted(Span<bNodeTree *> root_ntrees) + { + if (root_ntrees.is_empty()) { + return; + } + + bool is_single_tree_update = false; + + if (root_ntrees.size() == 1) { + bNodeTree *ntree = root_ntrees[0]; + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + return; + } + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + if (!result.interface_changed && !result.output_changed) { + is_single_tree_update = true; + } + } + + if (!is_single_tree_update) { + Vector<bNodeTree *> ntrees_in_order = this->get_tree_update_order(root_ntrees); + for (bNodeTree *ntree : ntrees_in_order) { + if (ntree->changed_flag == NTREE_CHANGED_NOTHING) { + continue; + } + if (!update_result_by_tree_.contains(ntree)) { + const TreeUpdateResult result = this->update_tree(*ntree); + update_result_by_tree_.add_new(ntree, result); + } + const TreeUpdateResult result = update_result_by_tree_.lookup(ntree); + Span<TreeNodePair> dependent_trees = relations_.get_group_node_users(ntree); + if (result.output_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_OUTPUT); + } + } + if (result.interface_changed) { + for (const TreeNodePair &pair : dependent_trees) { + add_node_tag(pair.first, pair.second, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + } + + for (const auto item : update_result_by_tree_.items()) { + bNodeTree *ntree = item.key; + const TreeUpdateResult &result = item.value; + + this->reset_changed_flags(*ntree); + + if (result.interface_changed) { + if (ntree->type == NTREE_GEOMETRY) { + relations_.ensure_modifier_users(); + for (const ObjectModifierPair &pair : relations_.get_modifier_users(ntree)) { + Object *object = pair.first; + ModifierData *md = pair.second; + + if (md->type == eModifierType_Nodes) { + MOD_nodes_update_interface(object, (NodesModifierData *)md); + } + } + } + } + + if (params_) { + relations_.ensure_owner_ids(); + ID *id = relations_.get_owner_id(ntree); + if (params_->tree_changed_fn) { + params_->tree_changed_fn(id, ntree, params_->user_data); + } + if (params_->tree_output_changed_fn && result.output_changed) { + params_->tree_output_changed_fn(id, ntree, params_->user_data); + } + } + } + } + + private: + enum class ToposortMark { + None, + Temporary, + Permanent, + }; + + using ToposortMarkMap = Map<bNodeTree *, ToposortMark>; + + /** + * Finds all trees that depend on the given trees (through node groups). Then those trees are + * ordered such that all trees used by one tree come before it. + */ + Vector<bNodeTree *> get_tree_update_order(Span<bNodeTree *> root_ntrees) + { + relations_.ensure_group_node_users(); + + Set<bNodeTree *> trees_to_update = get_trees_to_update(root_ntrees); + + Vector<bNodeTree *> sorted_ntrees; + + ToposortMarkMap marks; + for (bNodeTree *ntree : trees_to_update) { + marks.add_new(ntree, ToposortMark::None); + } + for (bNodeTree *ntree : trees_to_update) { + if (marks.lookup(ntree) == ToposortMark::None) { + const bool cycle_detected = !this->get_tree_update_order__visit_recursive( + ntree, marks, sorted_ntrees); + /* This should be prevented by higher level operators. */ + BLI_assert(!cycle_detected); + UNUSED_VARS_NDEBUG(cycle_detected); + } + } + + std::reverse(sorted_ntrees.begin(), sorted_ntrees.end()); + + return sorted_ntrees; + } + + bool get_tree_update_order__visit_recursive(bNodeTree *ntree, + ToposortMarkMap &marks, + Vector<bNodeTree *> &sorted_ntrees) + { + ToposortMark &mark = marks.lookup(ntree); + if (mark == ToposortMark::Permanent) { + return true; + } + if (mark == ToposortMark::Temporary) { + /* There is a dependency cycle. */ + return false; + } + + mark = ToposortMark::Temporary; + + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + this->get_tree_update_order__visit_recursive(pair.first, marks, sorted_ntrees); + } + sorted_ntrees.append(ntree); + + mark = ToposortMark::Permanent; + return true; + } + + Set<bNodeTree *> get_trees_to_update(Span<bNodeTree *> root_ntrees) + { + relations_.ensure_group_node_users(); + + Set<bNodeTree *> reachable_trees; + VectorSet<bNodeTree *> trees_to_check = root_ntrees; + + while (!trees_to_check.is_empty()) { + bNodeTree *ntree = trees_to_check.pop(); + if (reachable_trees.add(ntree)) { + for (const TreeNodePair &pair : relations_.get_group_node_users(ntree)) { + trees_to_check.add(pair.first); + } + } + } + + return reachable_trees; + } + + TreeUpdateResult update_tree(bNodeTree &ntree) + { + TreeUpdateResult result; + + /* Use a #NodeTreeRef to speedup certain queries. It is rebuilt whenever the node tree topology + * changes, which typically happens zero or one times during the entire update of the node + * tree. */ + std::unique_ptr<NodeTreeRef> tree_ref; + this->ensure_tree_ref(ntree, tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_individual_nodes(ntree, tree_ref); + this->update_internal_links(ntree, tree_ref); + this->update_generic_callback(ntree, tree_ref); + this->remove_unused_previews_when_necessary(ntree); + + this->ensure_tree_ref(ntree, tree_ref); + if (ntree.type == NTREE_GEOMETRY) { + if (node_field_inferencing::update_field_inferencing(*tree_ref)) { + result.interface_changed = true; + } + } + + result.output_changed = this->check_if_output_changed(*tree_ref); + + this->update_socket_link_and_use(*tree_ref); + this->update_node_levels(ntree); + this->update_link_validation(ntree); + + if (ntree.type == NTREE_TEXTURE) { + ntreeTexCheckCyclics(&ntree); + } + + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE || ntree.changed_flag & NTREE_CHANGED_ANY) { + result.interface_changed = true; + } + + if (result.interface_changed) { + ntreeInterfaceTypeUpdate(&ntree); + } + + return result; + } + + void ensure_tree_ref(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + if (!tree_ref) { + tree_ref = std::make_unique<NodeTreeRef>(&ntree); + } + } + + void update_socket_link_and_use(const NodeTreeRef &tree) + { + for (const InputSocketRef *socket : tree.input_sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + if (socket->directly_linked_links().is_empty()) { + bsocket->link = nullptr; + } + else { + bsocket->link = socket->directly_linked_links()[0]->blink(); + } + } + + this->update_socket_used_tags(tree); + } + + void update_socket_used_tags(const NodeTreeRef &tree) + { + for (const SocketRef *socket : tree.sockets()) { + bNodeSocket *bsocket = socket->bsocket(); + bsocket->flag &= ~SOCK_IN_USE; + for (const LinkRef *link : socket->directly_linked_links()) { + if (!link->is_muted()) { + bsocket->flag |= SOCK_IN_USE; + break; + } + } + } + } + + void update_individual_nodes(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + /* Iterate over nodes instead of #NodeTreeRef, because the #tree_ref might be outdated after + * some update functions. */ + LISTBASE_FOREACH (bNode *, bnode, &ntree.nodes) { + this->ensure_tree_ref(ntree, tree_ref); + const NodeRef &node = *tree_ref->find_node(*bnode); + if (this->should_update_individual_node(node)) { + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + /* This may set #ntree.changed_flag which is detected below. */ + this->update_individual_node(node); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. Generally, only very few update + * functions change the node. Typically zero or one nodes change after an update. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + } + } + + bool should_update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + if (ntree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + if (bnode.changed_flag & NTREE_CHANGED_NODE_PROPERTY) { + return true; + } + if (ntree.changed_flag & NTREE_CHANGED_LINK) { + /* Node groups currently always rebuilt their sockets when they are updated. + * So avoid calling the update method when no new link was added to it. */ + if (node.is_group_input_node()) { + if (node.outputs().last()->is_directly_linked()) { + return true; + } + } + else if (node.is_group_output_node()) { + if (node.inputs().last()->is_directly_linked()) { + return true; + } + } + else { + /* Currently we have no way to tell if a node needs to be updated when a link changed. */ + return true; + } + } + if (ntree.changed_flag & NTREE_CHANGED_INTERFACE) { + if (node.is_group_input_node() || node.is_group_output_node()) { + return true; + } + } + return false; + } + + void update_individual_node(const NodeRef &node) + { + bNodeTree &ntree = *node.btree(); + bNode &bnode = *node.bnode(); + bNodeType &ntype = *bnode.typeinfo; + if (ntype.group_update_func) { + ntype.group_update_func(&ntree, &bnode); + } + if (ntype.updatefunc) { + ntype.updatefunc(&ntree, &bnode); + } + } + + void update_internal_links(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + bool any_internal_links_updated = false; + this->ensure_tree_ref(ntree, tree_ref); + for (const NodeRef *node : tree_ref->nodes()) { + if (!this->should_update_individual_node(*node)) { + continue; + } + /* Find all expected internal links. */ + Vector<std::pair<bNodeSocket *, bNodeSocket *>> expected_internal_links; + for (const OutputSocketRef *output_socket : node->outputs()) { + if (!output_socket->is_available()) { + continue; + } + if (!output_socket->is_directly_linked()) { + continue; + } + if (output_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const InputSocketRef *input_socket = this->find_internally_linked_input(output_socket); + if (input_socket != nullptr) { + expected_internal_links.append({input_socket->bsocket(), output_socket->bsocket()}); + } + } + /* rebuilt internal links if they have changed. */ + if (node->internal_links().size() != expected_internal_links.size()) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + } + else { + for (auto &item : expected_internal_links) { + const bNodeSocket *from_socket = item.first; + const bNodeSocket *to_socket = item.second; + bool found = false; + for (const InternalLinkRef *internal_link : node->internal_links()) { + if (from_socket == internal_link->from().bsocket() && + to_socket == internal_link->to().bsocket()) { + found = true; + } + } + if (!found) { + this->update_internal_links_in_node(ntree, *node->bnode(), expected_internal_links); + any_internal_links_updated = true; + break; + } + } + } + } + + if (any_internal_links_updated) { + tree_ref.reset(); + } + } + + const InputSocketRef *find_internally_linked_input(const OutputSocketRef *output_socket) + { + const InputSocketRef *selected_socket = nullptr; + int selected_priority = -1; + bool selected_is_linked = false; + for (const InputSocketRef *input_socket : output_socket->node().inputs()) { + if (!input_socket->is_available()) { + continue; + } + if (input_socket->bsocket()->flag & SOCK_NO_INTERNAL_LINK) { + continue; + } + const int priority = get_internal_link_type_priority(input_socket->bsocket()->typeinfo, + output_socket->bsocket()->typeinfo); + if (priority < 0) { + continue; + } + const bool is_linked = input_socket->is_directly_linked(); + const bool is_preferred = priority > selected_priority || (is_linked && !selected_is_linked); + if (!is_preferred) { + continue; + } + selected_socket = input_socket; + selected_priority = priority; + selected_is_linked = is_linked; + } + return selected_socket; + } + + void update_internal_links_in_node(bNodeTree &ntree, + bNode &node, + Span<std::pair<bNodeSocket *, bNodeSocket *>> links) + { + BLI_freelistN(&node.internal_links); + for (const auto &item : links) { + bNodeSocket *from_socket = item.first; + bNodeSocket *to_socket = item.second; + bNodeLink *link = MEM_cnew<bNodeLink>(__func__); + link->fromnode = &node; + link->fromsock = from_socket; + link->tonode = &node; + link->tosock = to_socket; + link->flag |= NODE_LINK_VALID; + BLI_addtail(&node.internal_links, link); + } + BKE_ntree_update_tag_node_internal_link(&ntree, &node); + } + + void update_generic_callback(bNodeTree &ntree, std::unique_ptr<NodeTreeRef> &tree_ref) + { + if (ntree.typeinfo->update == nullptr) { + return; + } + + /* Reset the changed_flag to allow detecting when the update callback changed the node tree. */ + const uint32_t old_changed_flag = ntree.changed_flag; + ntree.changed_flag = NTREE_CHANGED_NOTHING; + + ntree.typeinfo->update(&ntree); + + if (ntree.changed_flag != NTREE_CHANGED_NOTHING) { + /* The tree ref is outdated and needs to be rebuilt. */ + tree_ref.reset(); + } + ntree.changed_flag |= old_changed_flag; + } + + void remove_unused_previews_when_necessary(bNodeTree &ntree) + { + /* Don't trigger preview removal when only those flags are set. */ + const uint32_t allowed_flags = NTREE_CHANGED_LINK | NTREE_CHANGED_SOCKET_PROPERTY | + NTREE_CHANGED_NODE_PROPERTY | NTREE_CHANGED_NODE_OUTPUT | + NTREE_CHANGED_INTERFACE; + if ((ntree.changed_flag & allowed_flags) == ntree.changed_flag) { + return; + } + BKE_node_preview_remove_unused(&ntree); + } + + void update_node_levels(bNodeTree &ntree) + { + ntreeUpdateNodeLevels(&ntree); + } + + void update_link_validation(bNodeTree &ntree) + { + LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) { + link->flag |= NODE_LINK_VALID; + if (link->fromnode && link->tonode && link->fromnode->level <= link->tonode->level) { + link->flag &= ~NODE_LINK_VALID; + } + else if (ntree.typeinfo->validate_link) { + const eNodeSocketDatatype from_type = static_cast<eNodeSocketDatatype>( + link->fromsock->type); + const eNodeSocketDatatype to_type = static_cast<eNodeSocketDatatype>(link->tosock->type); + if (!ntree.typeinfo->validate_link(from_type, to_type)) { + link->flag &= ~NODE_LINK_VALID; + } + } + } + } + + bool check_if_output_changed(const NodeTreeRef &tree) + { + bNodeTree &btree = *tree.btree(); + + /* Compute a hash that represents the node topology connected to the output. This always has to + * be updated even if it is not used to detect changes right now. Otherwise + * #btree.output_topology_hash will go out of date. */ + const Vector<const SocketRef *> tree_output_sockets = this->find_output_sockets(tree); + const uint32_t old_topology_hash = btree.output_topology_hash; + const uint32_t new_topology_hash = this->get_combined_socket_topology_hash( + tree, tree_output_sockets); + btree.output_topology_hash = new_topology_hash; + + if (const AnimData *adt = BKE_animdata_from_id(&btree.id)) { + /* Drivers may copy values in the node tree around arbitrarily and may cause the output to + * change even if it wouldn't without drivers. Only some special drivers like `frame/5` can + * be used without causing updates all the time currently. In the future we could try to + * handle other drivers better as well. + * Note that this optimization only works in practice when the depsgraph didn't also get a + * copy-on-write tag for the node tree (which happens when changing node properties). It does + * work in a few situations like adding reroutes and duplicating nodes though. */ + LISTBASE_FOREACH (const FCurve *, fcurve, &adt->drivers) { + const ChannelDriver *driver = fcurve->driver; + const StringRef expression = driver->expression; + if (expression.startswith("frame")) { + const StringRef remaining_expression = expression.drop_known_prefix("frame"); + if (remaining_expression.find_first_not_of(" */+-0123456789.") == StringRef::not_found) { + continue; + } + } + /* Unrecognized driver, assume that the output always changes. */ + return true; + } + } + + if (btree.changed_flag & NTREE_CHANGED_ANY) { + return true; + } + + if (old_topology_hash != new_topology_hash) { + return true; + } + + /* The topology hash can only be used when only topology-changing operations have been done. */ + if (btree.changed_flag == + (btree.changed_flag & (NTREE_CHANGED_LINK | NTREE_CHANGED_REMOVED_NODE))) { + if (old_topology_hash == new_topology_hash) { + return false; + } + } + + if (!this->check_if_socket_outputs_changed_based_on_flags(tree, tree_output_sockets)) { + return false; + } + + return true; + } + + Vector<const SocketRef *> find_output_sockets(const NodeTreeRef &tree) + { + Vector<const SocketRef *> sockets; + for (const NodeRef *node : tree.nodes()) { + const bNode *bnode = node->bnode(); + if (bnode->typeinfo->nclass != NODE_CLASS_OUTPUT && bnode->type != NODE_GROUP_OUTPUT) { + continue; + } + for (const InputSocketRef *socket : node->inputs()) { + if (socket->idname() != "NodeSocketVirtual") { + sockets.append(socket); + } + } + } + return sockets; + } + + /** + * Computes a hash that changes when the node tree topology connected to an output node changes. + * Adding reroutes does not have an effect on the hash. + */ + uint32_t get_combined_socket_topology_hash(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + if (tree.has_link_cycles()) { + /* Return dummy value when the link has any cycles. The algorithm below could be improved to + * handle cycles more gracefully. */ + return 0; + } + Array<uint32_t> hashes = this->get_socket_topology_hashes(tree, sockets); + uint32_t combined_hash = 0; + for (uint32_t hash : hashes) { + combined_hash = noise::hash(combined_hash, hash); + } + return combined_hash; + } + + Array<uint32_t> get_socket_topology_hashes(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + BLI_assert(!tree.has_link_cycles()); + Array<std::optional<uint32_t>> hash_by_socket_id(tree.sockets().size()); + Stack<const SocketRef *> sockets_to_check = sockets; + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.peek(); + const NodeRef &node = in_out_socket.node(); + + if (hash_by_socket_id[in_out_socket.id()].has_value()) { + sockets_to_check.pop(); + /* Socket is handled already. */ + continue; + } + + if (in_out_socket.is_input()) { + /* For input sockets, first compute the hashes of all linked sockets. */ + const InputSocketRef &socket = in_out_socket.as_input(); + bool all_origins_computed = true; + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + if (!hash_by_socket_id[origin_socket->id()].has_value()) { + sockets_to_check.push(origin_socket); + all_origins_computed = false; + } + } + if (!all_origins_computed) { + continue; + } + /* When the hashes for the linked sockets are ready, combine them into a hash for the input + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + const uint32_t origin_socket_hash = *hash_by_socket_id[origin_socket->id()]; + socket_hash = noise::hash(socket_hash, origin_socket_hash); + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + else { + /* For output sockets, first compute the hashes of all available input sockets. */ + const OutputSocketRef &socket = in_out_socket.as_output(); + bool all_available_inputs_computed = true; + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + if (!hash_by_socket_id[input_socket->id()].has_value()) { + sockets_to_check.push(input_socket); + all_available_inputs_computed = false; + } + } + } + if (!all_available_inputs_computed) { + continue; + } + /* When all input socket hashes have been computed, combine them into a hash for the output + * socket. */ + const uint64_t socket_ptr = (uintptr_t)socket.bsocket(); + uint32_t socket_hash = noise::hash(socket_ptr, socket_ptr >> 32); + for (const InputSocketRef *input_socket : node.inputs()) { + if (input_socket->is_available()) { + const uint32_t input_socket_hash = *hash_by_socket_id[input_socket->id()]; + socket_hash = noise::hash(socket_hash, input_socket_hash); + } + } + hash_by_socket_id[socket.id()] = socket_hash; + sockets_to_check.pop(); + } + } + + /* Create output array. */ + Array<uint32_t> hashes(sockets.size()); + for (const int i : sockets.index_range()) { + hashes[i] = *hash_by_socket_id[sockets[i]->id()]; + } + return hashes; + } + + /** + * Returns true when any of the provided sockets changed its values. A change is detected by + * checking the #changed_flag on connected sockets and nodes. + */ + bool check_if_socket_outputs_changed_based_on_flags(const NodeTreeRef &tree, + Span<const SocketRef *> sockets) + { + /* Avoid visiting the same socket twice when multiple links point to the same socket. */ + Array<bool> pushed_by_socket_id(tree.sockets().size(), false); + Stack<const SocketRef *> sockets_to_check = sockets; + + for (const SocketRef *socket : sockets) { + pushed_by_socket_id[socket->id()] = true; + } + + while (!sockets_to_check.is_empty()) { + const SocketRef &in_out_socket = *sockets_to_check.pop(); + const bNode &bnode = *in_out_socket.node().bnode(); + const bNodeSocket &bsocket = *in_out_socket.bsocket(); + if (bsocket.changed_flag != NTREE_CHANGED_NOTHING) { + return true; + } + if (bnode.changed_flag != NTREE_CHANGED_NOTHING) { + const bool only_unused_internal_link_changed = (bnode.flag & NODE_MUTED) == 0 && + bnode.changed_flag == + NTREE_CHANGED_INTERNAL_LINK; + if (!only_unused_internal_link_changed) { + return true; + } + } + if (in_out_socket.is_input()) { + const InputSocketRef &socket = in_out_socket.as_input(); + for (const OutputSocketRef *origin_socket : socket.logically_linked_sockets()) { + bool &pushed = pushed_by_socket_id[origin_socket->id()]; + if (!pushed) { + sockets_to_check.push(origin_socket); + pushed = true; + } + } + } + else { + const OutputSocketRef &socket = in_out_socket.as_output(); + for (const InputSocketRef *input_socket : socket.node().inputs()) { + if (input_socket->is_available()) { + bool &pushed = pushed_by_socket_id[input_socket->id()]; + if (!pushed) { + sockets_to_check.push(input_socket); + pushed = true; + } + } + } + } + } + return false; + } + + void reset_changed_flags(bNodeTree &ntree) + { + ntree.changed_flag = NTREE_CHANGED_NOTHING; + LISTBASE_FOREACH (bNode *, node, &ntree.nodes) { + node->changed_flag = NTREE_CHANGED_NOTHING; + node->update = 0; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + socket->changed_flag = NTREE_CHANGED_NOTHING; + } + } + } +}; + +} // namespace blender::bke + +void BKE_ntree_update_tag_all(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ANY); +} + +void BKE_ntree_update_tag_node_property(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_node_new(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_socket_property(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_new(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_SOCKET); +} + +void BKE_ntree_update_tag_socket_type(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_socket_availability(bNodeTree *ntree, bNodeSocket *socket) +{ + add_socket_tag(ntree, socket, NTREE_CHANGED_SOCKET_PROPERTY); +} + +void BKE_ntree_update_tag_node_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_REMOVED_NODE); +} + +void BKE_ntree_update_tag_node_mute(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); +} + +void BKE_ntree_update_tag_node_internal_link(bNodeTree *ntree, bNode *node) +{ + add_node_tag(ntree, node, NTREE_CHANGED_INTERNAL_LINK); +} + +void BKE_ntree_update_tag_link_changed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_removed(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_added(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_link_mute(bNodeTree *ntree, bNodeLink *UNUSED(link)) +{ + add_tree_tag(ntree, NTREE_CHANGED_LINK); +} + +void BKE_ntree_update_tag_missing_runtime_data(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_ALL); +} + +void BKE_ntree_update_tag_interface(bNodeTree *ntree) +{ + add_tree_tag(ntree, NTREE_CHANGED_INTERFACE); +} + +void BKE_ntree_update_tag_id_changed(Main *bmain, ID *id) +{ + FOREACH_NODETREE_BEGIN (bmain, ntree, ntree_id) { + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->id == id) { + node->update |= NODE_UPDATE_ID; + add_node_tag(ntree, node, NTREE_CHANGED_NODE_PROPERTY); + } + } + } + FOREACH_NODETREE_END; +} + +/** + * Protect from recursive calls into the updating function. Some node update functions might + * trigger this from Python or in other cases. + * + * This could be added to #Main, but given that there is generally only one #Main, that's not + * really worth it now. + */ +static bool is_updating = false; + +void BKE_ntree_update_main(Main *bmain, NodeTreeUpdateExtraParams *params) +{ + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update(); + is_updating = false; +} + +void BKE_ntree_update_main_tree(Main *bmain, bNodeTree *ntree, NodeTreeUpdateExtraParams *params) +{ + if (ntree == nullptr) { + BKE_ntree_update_main(bmain, params); + return; + } + + if (is_updating) { + return; + } + + is_updating = true; + blender::bke::NodeTreeMainUpdater updater{bmain, params}; + updater.update_rooted({ntree}); + is_updating = false; +} diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.cc index fbdf99c91c2..e177b1ce29e 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,9 +82,12 @@ #include "BKE_anim_visualization.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_asset.h" +#include "BKE_bpath.h" #include "BKE_camera.h" #include "BKE_collection.h" #include "BKE_constraint.h" +#include "BKE_crazyspace.h" #include "BKE_curve.h" #include "BKE_deform.h" #include "BKE_displist.h" @@ -94,8 +97,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 +139,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" @@ -158,11 +162,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 @@ -200,24 +203,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_msg( - 0, "Object copy: non-NULL material pointers with zero counter, should not happen."); - ob_dst->mat = NULL; - ob_dst->matbits = NULL; + 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); @@ -233,7 +236,7 @@ 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); } } @@ -241,12 +244,12 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in 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); @@ -268,7 +271,7 @@ static void object_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const in BKE_previewimg_id_copy(&ob_dst->id, &ob_src->id); } else { - ob_dst->preview = NULL; + ob_dst->preview = nullptr; } } @@ -290,17 +293,17 @@ static void object_free_data(ID *id) 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); @@ -316,7 +319,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); @@ -331,47 +334,26 @@ static void object_make_local(Main *bmain, ID *id, const int flags) 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) - * - only local users: set flag - * - mixed: make copy - * In case we make a whole lib's content local, - * we always want to localize, and we skip remapping (done later). - */ - - 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; - } - } - } + bool force_local, force_copy; + BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy); if (force_local) { - BKE_lib_id_clear_library_data(bmain, &ob->id); - BKE_lib_id_expand_local(bmain, &ob->id); + 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 != NULL) { - ob->proxy_from->proxy = NULL; - ob->proxy_from->proxy_group = NULL; + if (ob->proxy_from != nullptr) { + ob->proxy_from->proxy = nullptr; + ob->proxy_from->proxy_group = nullptr; } - ob->proxy = ob->proxy_from = ob->proxy_group = NULL; + ob->proxy = ob->proxy_from = ob->proxy_group = nullptr; } } 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); @@ -388,7 +370,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, @@ -397,7 +380,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, @@ -406,7 +390,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), @@ -416,7 +401,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), @@ -425,7 +411,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) @@ -441,22 +428,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), @@ -464,26 +451,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. */ @@ -491,38 +479,117 @@ 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 object_foreach_path_pointcache(ListBase *ptcache_list, + BPathForeachPathData *bpath_data) +{ + for (PointCache *cache = (PointCache *)ptcache_list->first; cache != nullptr; + cache = cache->next) { + if (cache->flag & PTCACHE_DISK_CACHE) { + BKE_bpath_foreach_path_fixed_process(bpath_data, cache->path); + } + } +} + +static void object_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Object *ob = reinterpret_cast<Object *>(id); + + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + /* TODO: Move that to #ModifierTypeInfo. */ + switch (md->type) { + case eModifierType_Fluidsim: { + FluidsimModifierData *fluidmd = reinterpret_cast<FluidsimModifierData *>(md); + if (fluidmd->fss) { + BKE_bpath_foreach_path_fixed_process(bpath_data, fluidmd->fss->surfdataPath); + } + break; + } + case eModifierType_Fluid: { + FluidModifierData *fmd = reinterpret_cast<FluidModifierData *>(md); + if (fmd->type & MOD_FLUID_TYPE_DOMAIN && fmd->domain) { + BKE_bpath_foreach_path_fixed_process(bpath_data, fmd->domain->cache_directory); + } + break; + } + case eModifierType_Cloth: { + ClothModifierData *clmd = reinterpret_cast<ClothModifierData *>(md); + object_foreach_path_pointcache(&clmd->ptcaches, bpath_data); + break; + } + case eModifierType_Ocean: { + OceanModifierData *omd = reinterpret_cast<OceanModifierData *>(md); + BKE_bpath_foreach_path_fixed_process(bpath_data, omd->cachepath); + break; + } + case eModifierType_MeshCache: { + MeshCacheModifierData *mcmd = reinterpret_cast<MeshCacheModifierData *>(md); + BKE_bpath_foreach_path_fixed_process(bpath_data, mcmd->filepath); + break; + } + default: + break; + } + } + + if (ob->soft != nullptr) { + object_foreach_path_pointcache(&ob->soft->shared->ptcaches, bpath_data); + } + + LISTBASE_FOREACH (ParticleSystem *, psys, &ob->particlesystem) { + object_foreach_path_pointcache(&psys->ptcaches, bpath_data); + } +} + static void write_fmaps(BlendWriter *writer, ListBase *fbase) { LISTBASE_FOREACH (bFaceMap *, fmap, fbase) { @@ -557,9 +624,9 @@ static void object_blend_write(BlendWriter *writer, ID *id, const void *id_addre BLO_write_pointer_array(writer, ob->totcol, ob->mat); BLO_write_raw(writer, sizeof(char) * ob->totcol, ob->matbits); - bArmature *arm = NULL; + bArmature *arm = nullptr; if (ob->type == OB_ARMATURE) { - arm = ob->data; + 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)); } @@ -621,7 +688,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)) { @@ -665,10 +732,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; @@ -721,9 +788,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); @@ -735,13 +802,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); @@ -757,11 +824,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); @@ -771,7 +838,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); @@ -808,7 +875,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); @@ -844,6 +911,7 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id) { Object *ob = (Object *)id; + Main *bmain = BLO_read_lib_get_main(reader); BlendFileReadReport *reports = BLO_read_lib_reports(reader); /* XXX deprecated - old animation system <<< */ @@ -860,7 +928,7 @@ 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(reports, RPT_INFO, @@ -869,7 +937,7 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id) ob->id.name + 2, new_id->name + 2); } - ob->instance_collection = NULL; + ob->instance_collection = nullptr; ob->transflag &= ~OB_DUPLICOLLECTION; } @@ -877,8 +945,8 @@ static void object_blend_read_lib(BlendLibReader *reader, ID *id) if (ob->proxy) { /* paranoia check, actually a proxy_from pointer should never be written... */ if (!ID_IS_LINKED(ob->proxy)) { - ob->proxy->proxy_from = NULL; - ob->proxy = NULL; + ob->proxy->proxy_from = nullptr; + ob->proxy = nullptr; if (ob->id.lib) { BLO_reportf_wrap(reports, @@ -903,7 +971,7 @@ 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->data == nullptr && poin != nullptr) { ob->type = OB_EMPTY; if (ob->pose) { @@ -917,7 +985,7 @@ 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; } @@ -940,12 +1008,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); @@ -1028,7 +1091,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); } @@ -1036,14 +1099,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) @@ -1131,46 +1194,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, + /* asset_type_info */ &AssetType_OB, + + /* 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, + /* foreach_path */ object_foreach_path, + /* 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, }; void BKE_object_workob_clear(Object *workob) @@ -1186,7 +1334,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); } } @@ -1206,7 +1354,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; } } @@ -1215,11 +1363,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 */ @@ -1236,7 +1384,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); } } @@ -1266,7 +1414,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 */ @@ -1288,19 +1436,13 @@ 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 - * it must be in the #Object.modifiers list. - */ void BKE_object_modifier_set_active(Object *ob, ModifierData *md) { LISTBASE_FOREACH (ModifierData *, md_iter, &ob->modifiers) { md_iter->flag &= ~eModifierFlag_Active; } - if (md != NULL) { + if (md != nullptr) { BLI_assert(BLI_findindex(&ob->modifiers, md) != -1); md->flag |= eModifierFlag_Active; } @@ -1325,12 +1467,9 @@ ModifierData *BKE_object_active_modifier(const Object *ob) } } - return NULL; + return nullptr; } -/** - * \return True if the object's type supports regular modifiers (not grease pencil modifiers). - */ bool BKE_object_supports_modifiers(const Object *ob) { return ( @@ -1339,19 +1478,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 != NULL && ELEM(ob->type, OB_SURF, OB_LATTICE)) { + 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) { @@ -1388,7 +1527,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. */ @@ -1400,7 +1539,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; } @@ -1408,24 +1547,13 @@ static ParticleSystem *object_copy_modifier_particle_system_ensure(Main *bmain, return psys_dst; } -/** - * Copy a single modifier. - * - * \note **Do not** use this function to copy a whole modifier stack (see note below too). Use - * `BKE_object_modifier_stack_copy` instead. - * - * \note Complex modifiers relaying on other data (like e.g. dynamic paint or fluid using particle - * systems) are not always 100% 'correctly' copied here, since we have to use heuristics to decide - * which particle system to use or add in `ob_dst`, and it's placement in the stack, etc. If used - * more than once, this function should preferably be called in stack order. - */ bool BKE_object_copy_modifier( Main *bmain, Scene *scene, Object *ob_dst, const Object *ob_src, ModifierData *md_src) { 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; } @@ -1433,13 +1561,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: @@ -1447,12 +1575,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); } @@ -1461,7 +1589,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); } @@ -1491,17 +1619,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; @@ -1518,12 +1646,6 @@ bool BKE_object_copy_modifier( return true; } -/** - * Copy a single GPencil modifier. - * - * \note **Do not** use this function to copy a whole modifier stack. Use - * `BKE_object_modifier_stack_copy` instead. - */ bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, GpencilModifierData *gmd_src) { BLI_assert(ob_dst->type == OB_GPENCIL); @@ -1531,7 +1653,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); @@ -1540,15 +1663,6 @@ bool BKE_object_copy_gpencil_modifier(struct Object *ob_dst, GpencilModifierData return true; } -/** - * Copy the whole stack of modifiers from one object into another. - * - * \warning **Does not** clear modifier stack and related data (particle systems, soft-body, - * etc.) in `ob_dst`, if needed calling code must do it. - * - * \param do_copy_all: If true, even modifiers that should not support copying (like Hook one) - * will be duplicated. - */ bool BKE_object_modifier_stack_copy(Object *ob_dst, const Object *ob_src, const bool do_copy_all, @@ -1568,7 +1682,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)) { @@ -1621,7 +1735,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); } @@ -1639,13 +1753,14 @@ static void object_update_from_subsurf_ccg(Object *object) if (!object->runtime.is_data_eval_owned) { return; } - /* Object was never evaluated, so can not have CCG subdivision surface. */ - Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object); - if (mesh_eval == NULL) { + /* Object was never evaluated, so can not have CCG subdivision surface. If it were evaluated, do + * not try to compute OpenSubDiv on the CPU as it is not needed here. */ + Mesh *mesh_eval = BKE_object_get_evaluated_mesh_no_subsurf(object); + 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. */ @@ -1659,24 +1774,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 + * 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. */ @@ -1687,7 +1802,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); @@ -1697,13 +1812,10 @@ static void object_update_from_subsurf_ccg(Object *object) subdiv_ccg->dirty.hidden = false; } -/** - * Assign #Object.data after modifier stack evaluation. - */ 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) { @@ -1715,8 +1827,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. */ @@ -1726,19 +1838,22 @@ 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; } -/** - * Free data derived from mesh, called when mesh changes or is freed. - */ void BKE_object_free_derived_caches(Object *ob) { MEM_SAFE_FREE(ob->runtime.bb); object_update_from_subsurf_ccg(ob); - if (ob->runtime.data_eval != NULL) { + if (ob->runtime.editmesh_eval_cage && + ob->runtime.editmesh_eval_cage != reinterpret_cast<Mesh *>(ob->runtime.data_eval)) { + BKE_mesh_eval_delete(ob->runtime.editmesh_eval_cage); + } + ob->runtime.editmesh_eval_cage = nullptr; + + 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) { @@ -1749,17 +1864,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; } @@ -1767,16 +1882,20 @@ void BKE_object_free_derived_caches(Object *ob) BKE_object_to_curve_clear(ob); BKE_object_free_curve_cache(ob); + BKE_crazyspace_api_eval_clear(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; + ob->runtime.geometry_set_eval = nullptr; } + + MEM_SAFE_FREE(ob->runtime.editmesh_bb_cage); } void BKE_object_free_caches(Object *object) @@ -1785,8 +1904,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; } @@ -1797,11 +1915,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; @@ -1827,29 +1945,26 @@ void BKE_object_free_caches(Object *object) } } -/** - * 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); @@ -1869,15 +1984,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; @@ -1890,15 +2006,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; } } @@ -1927,16 +2043,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)); } @@ -1966,7 +2082,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; } } @@ -1978,9 +2094,6 @@ bool BKE_object_is_mode_compat(const struct Object *ob, eObjectMode object_mode) return ((ob->mode == object_mode) || (ob->mode & object_mode) != 0); } -/** - * Return which parts of the object are visible, as evaluated by depsgraph - */ int BKE_object_visibility(const Object *ob, const int dag_eval_mode) { if ((ob->base_flag & BASE_VISIBLE_DEPSGRAPH) == 0) { @@ -2021,7 +2134,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; } @@ -2105,7 +2218,7 @@ static void object_init(Object *ob, const short ob_type) 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); } @@ -2141,16 +2254,13 @@ 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; } } -/** - * Return -1 on failure. - */ int BKE_object_obdata_to_type(const ID *id) { /* Keep in sync with #OB_DATA_SUPPORT_ID macro. */ @@ -2186,9 +2296,6 @@ int BKE_object_obdata_to_type(const ID *id) } } -/** - * More general add: creates minimum required data, but without vertices etc. - */ Object *BKE_object_add_only_object(Main *bmain, int type, const char *name) { if (!name) { @@ -2196,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); @@ -2218,14 +2325,6 @@ static Object *object_add_common(Main *bmain, ViewLayer *view_layer, int type, c return ob; } -/** - * General add: to scene, with layer from area and default name - * - * Object is added to the active #Collection. - * If there is no linked collection to the active #ViewLayer we create a new one. - * - * \note Creates minimum required data, but without vertices etc. - */ Object *BKE_object_add(Main *bmain, ViewLayer *view_layer, int type, const char *name) { Object *ob = object_add_common(bmain, view_layer, type, name); @@ -2239,11 +2338,6 @@ Object *BKE_object_add(Main *bmain, ViewLayer *view_layer, int type, const char return ob; } -/** - * Add a new object, using another one as a reference - * - * \param ob_src: object to use to determine the collections of the new object. - */ Object *BKE_object_add_from( Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name, Object *ob_src) { @@ -2256,21 +2350,12 @@ Object *BKE_object_add_from( return ob; } -/** - * Add a new object, but assign the given datablock 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. - */ 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); } @@ -2294,17 +2379,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; @@ -2313,33 +2398,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; @@ -2347,26 +2432,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); @@ -2376,14 +2461,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(campbell): from reading existing code this seems correct but intended usage of - * pointcache should /w cloth should be added in 'ParticleSystem'. */ + /* 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; } @@ -2443,7 +2528,7 @@ static void copy_object_pose(Object *obn, const Object *ob, const int flag) { /* 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) { @@ -2454,20 +2539,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); } } } @@ -2485,8 +2569,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)) { @@ -2500,7 +2584,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) @@ -2514,12 +2598,9 @@ Object *BKE_object_pose_armature_get_visible(Object *ob, ViewLayer *view_layer, } } } - return NULL; + return nullptr; } -/** - * Access pose array with special check to get pose object when in weight paint mode. - */ Object **BKE_object_pose_array_get_ex(ViewLayer *view_layer, View3D *v3d, uint *r_objects_len, @@ -2527,24 +2608,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; } @@ -2563,9 +2643,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) { @@ -2577,22 +2657,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; } @@ -2616,28 +2695,20 @@ void BKE_object_transform_copy(Object *ob_tar, const Object *ob_src) copy_v3_v3(ob_tar->scale, ob_src->scale); } -/** - * Perform deep-copy of object and its 'children' data-blocks (obdata, materials, actions, etc.). - * - * \param dupflag: Controls which sub-data are also duplicated - * (see #eDupli_ID_Flags in DNA_userdef_types.h). - * - * \note This function does not do any remapping to new IDs, caller must do it - * (\a #BKE_libblock_relink_to_newid()). - * \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, - 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. */ @@ -2649,7 +2720,7 @@ Object *BKE_object_duplicate(Main *bmain, 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) { @@ -2658,105 +2729,104 @@ Object *BKE_object_duplicate(Main *bmain, 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); } } } @@ -2764,7 +2834,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. */ @@ -2787,24 +2857,18 @@ 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); } return obn; } -/** - * Returns true if the Object is from an external blend file (libdata). - */ bool BKE_object_is_libdata(const Object *ob) { return (ob && ID_IS_LINKED(ob)); } -/** - * Returns true if the Object data is from an external blend file (libdata). - */ bool BKE_object_obdata_is_libdata(const Object *ob) { /* Linked objects with local obdata are forbidden! */ @@ -2819,11 +2883,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); } @@ -2833,7 +2896,6 @@ 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) { @@ -2844,11 +2906,10 @@ void BKE_object_copy_proxy_drivers(Object *ob, Object *target) 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) { @@ -2871,12 +2932,6 @@ void BKE_object_copy_proxy_drivers(Object *ob, Object *target) } } -/** - * Proxy rule: - * - lib_object->proxy_from == the one we borrow from, set temporally while object_update. - * - local_object->proxy == pointer to library object, saved in files and read. - * - local_object->proxy_group == pointer to collection dupli-object, saved in files and read. - */ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob) { /* paranoia checks */ @@ -2933,16 +2988,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 */ @@ -2952,9 +3007,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); } @@ -2966,7 +3021,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); @@ -2976,10 +3031,6 @@ void BKE_object_make_proxy(Main *bmain, Object *ob, Object *target, Object *cob) ob->dt = target->dt; } -/** - * Use with newly created objects to set their size - * (used to apply scene-scale). - */ void BKE_object_obdata_size_init(struct Object *ob, const float size) { /* apply radius as a scale to types that support it */ @@ -2989,17 +3040,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; @@ -3009,7 +3060,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); @@ -3252,7 +3303,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 @@ -3263,10 +3314,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; } @@ -3291,7 +3342,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); @@ -3348,9 +3399,10 @@ 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); + Mesh *me_eval = (em) ? BKE_object_get_editmesh_eval_final(par) : + BKE_object_get_evaluated_mesh(par); if (me_eval) { int count = 0; @@ -3382,7 +3434,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) { @@ -3421,24 +3473,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) { @@ -3448,7 +3500,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) { @@ -3593,7 +3645,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); } @@ -3615,20 +3667,14 @@ 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); } -/** - * Calculate object transformation matrix without recalculating dependencies and - * constraints -- assume dependencies are already solved by depsgraph. - * No changes to object and its parent would be done. - * Used for bundles orientation in 3d space relative to parented blender camera. - */ 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); @@ -3644,17 +3690,9 @@ 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); } -/** - * For calculation of the inverse parent transform, only used for editor. - * - * It assumes the object parent is already in the depsgraph. - * Otherwise, after changing ob->parent you need to call: - * - #DEG_relations_tag_update(bmain); - * - #BKE_scene_graph_update_tagged(depsgraph, bmain); - */ void BKE_object_workob_calc_parent(Depsgraph *depsgraph, Scene *scene, Object *ob, Object *workob) { BKE_object_workob_clear(workob); @@ -3664,7 +3702,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; @@ -3686,16 +3724,6 @@ void BKE_object_workob_calc_parent(Depsgraph *depsgraph, Scene *scene, Object *o BKE_object_where_is_calc(depsgraph, scene, workob); } -/** - * Applies the global transformation \a mat to the \a ob using a relative parent space if - * supplied. - * - * \param mat: the global transformation mat that the object should be set object to. - * \param parent: the parent space in which this object will be set relative to - * (should probably always be parent_eval). - * \param use_compat: true to ensure that rotations are set using the - * min difference between the old and new orientation. - */ void BKE_object_apply_mat4_ex(Object *ob, const float mat[4][4], Object *parent, @@ -3706,7 +3734,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); @@ -3739,15 +3767,12 @@ void BKE_object_apply_mat4_ex(Object *ob, /* BKE_object_mat3_to_rot handles delta rotations */ } -/** - * XXX: should be removed after COW operators port to use BKE_object_apply_mat4_ex directly. - */ void BKE_object_apply_mat4(Object *ob, const float mat[4][4], 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); } /** \} */ @@ -3756,11 +3781,11 @@ void BKE_object_apply_mat4(Object *ob, /** \name Object Bounding Box API * \{ */ -BoundBox *BKE_boundbox_alloc_unit(void) +BoundBox *BKE_boundbox_alloc_unit() { 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 = MEM_cnew<BoundBox>("OB-BoundBox"); BKE_boundbox_init_from_minmax(bb, min, max); return bb; @@ -3807,7 +3832,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: @@ -3845,9 +3870,6 @@ BoundBox *BKE_object_boundbox_get(Object *ob) return bb; } -/** - * Use this to temporally disable/enable bound-box. - */ void BKE_object_boundbox_flag(Object *ob, int flag, const bool set) { BoundBox *bb = BKE_object_boundbox_get(ob); @@ -3861,7 +3883,7 @@ void BKE_object_boundbox_flag(Object *ob, int flag, const bool set) } } -void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me_eval) +void BKE_object_boundbox_calc_from_mesh(Object *ob, const Mesh *me_eval) { float min[3], max[3]; @@ -3872,13 +3894,48 @@ void BKE_object_boundbox_calc_from_mesh(struct Object *ob, const struct Mesh *me 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 = MEM_cnew<BoundBox>("DM-BoundBox"); + } + + BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); + + ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; +} + +bool BKE_object_boundbox_calc_from_evaluated_geometry(Object *ob) +{ + blender::float3 min, max; + INIT_MINMAX(min, max); + + if (ob->runtime.geometry_set_eval) { + if (!ob->runtime.geometry_set_eval->compute_boundbox_without_instances(&min, &max)) { + zero_v3(min); + zero_v3(max); + } + } + else if (const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob)) { + if (!BKE_mesh_wrapper_minmax(mesh_eval, min, max)) { + zero_v3(min); + zero_v3(max); + } + } + else if (ob->runtime.curve_cache) { + BKE_displist_minmax(&ob->runtime.curve_cache->disp, min, max); + } + else { + return false; + } + + if (ob->runtime.bb == nullptr) { + ob->runtime.bb = MEM_cnew<BoundBox>(__func__); } BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); ob->runtime.bb->flag &= ~BOUNDBOX_DIRTY; + + return true; } /** \} */ @@ -3906,14 +3963,6 @@ void BKE_object_dimensions_get(Object *ob, float r_vec[3]) } } -/** - * The original scale and object matrix can be passed in so any difference - * of the objects matrix and the final matrix can be accounted for, - * typically this caused by parenting, constraints or delta-scale. - * - * Re-using these values from the object causes a feedback loop - * when multiple values are modified at once in some situations. see: T69536. - */ void BKE_object_dimensions_set_ex(Object *ob, const float value[3], int axis_mask, @@ -3931,7 +3980,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; @@ -3949,7 +3998,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) @@ -3979,7 +4028,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; @@ -4001,7 +4050,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); @@ -4055,8 +4104,7 @@ 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 = MEM_cnew<ImageUser>("image user"); ob->iuser->flag |= IMA_ANIM_ALWAYS; ob->iuser->frames = 100; ob->iuser->sfra = 1; @@ -4131,13 +4179,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 */ } @@ -4173,7 +4220,7 @@ static void foreach_display_point_gpencil_stroke_fn(bGPDlayer *UNUSED(layer), bGPDstroke *stroke, void *thunk) { - struct GPencilStrokePointIterData *iter_data = thunk; + GPencilStrokePointIterData *iter_data = (GPencilStrokePointIterData *)thunk; { bGPDspoint *pt; int i; @@ -4194,7 +4241,7 @@ void BKE_object_foreach_display_point(Object *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++) { @@ -4203,16 +4250,16 @@ void BKE_object_foreach_display_point(Object *ob, } } else if (ob->type == OB_GPENCIL) { - struct GPencilStrokePointIterData iter_data = { - .obmat = obmat, .point_func_cb = func_cb, .user_data = user_data}; + GPencilStrokePointIterData iter_data{}; + iter_data.obmat = obmat; + iter_data.point_func_cb = func_cb; + iter_data.user_data = user_data; BKE_gpencil_visible_stroke_iter( - ob->data, NULL, foreach_display_point_gpencil_stroke_fn, &iter_data); + (bGPdata *)ob->data, nullptr, foreach_display_point_gpencil_stroke_fn, &iter_data); } else if (ob->runtime.curve_cache && ob->runtime.curve_cache->disp.first) { - DispList *dl; - - for (dl = ob->runtime.curve_cache->disp.first; dl; dl = dl->next) { + LISTBASE_FOREACH (DispList *, dl, &ob->runtime.curve_cache->disp) { const float *v3 = dl->verts; int totvert = dl->nr; int i; @@ -4240,35 +4287,31 @@ void BKE_scene_foreach_display_point(Depsgraph *depsgraph, DEG_OBJECT_ITER_END; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Transform Channels (Backup/Restore) + * \{ */ + /** - * 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); @@ -4310,17 +4353,11 @@ void BKE_object_tfm_restore(Object *ob, void *obtfm_pt) copy_m4_m4(ob->imat, obtfm->imat); } -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) { - return false; - } - if (ob == par) { - return true; - } - return BKE_object_parent_loop_check(par->parent, ob); -} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Evaluation/Update API + * \{ */ static void object_handle_update_proxy(Depsgraph *depsgraph, Scene *scene, @@ -4328,7 +4365,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 */ @@ -4336,7 +4373,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); @@ -4344,35 +4381,23 @@ static void object_handle_update_proxy(Depsgraph *depsgraph, } } -/** - * Proxy rule: - * - lib_object->proxy_from == the one we borrow from, only set temporal and cleared here. - * - local_object->proxy == pointer to library object, saved in files and read. - * - * Function below is polluted with proxy exceptions, cleanup will follow! - * - * The main object update call, for object matrix, constraints, keys and displist (modifiers) - * requires flags to be set! - * - * Ideally we shouldn't have to pass the rigid body world, - * but need bigger restructuring to avoid id. - */ void BKE_object_handle_update_ex(Depsgraph *depsgraph, Scene *scene, Object *ob, 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); @@ -4384,9 +4409,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); } } } @@ -4399,7 +4424,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); } } @@ -4410,29 +4435,22 @@ void BKE_object_handle_update_ex(Depsgraph *depsgraph, object_handle_update_proxy(depsgraph, scene, ob, do_proxy_update); } -/** - * \warning "scene" here may not be the scene object actually resides in. - * When dealing with background-sets, "scene" is actually the active scene. - * e.g. "scene" <-- set 1 <-- set 2 ("ob" lives here) <-- set 3 <-- ... <-- set n - * rigid bodies depend on their world so use #BKE_object_handle_update_ex() - * to also pass along the current rigid body world. - */ 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 = MEM_cnew<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) +bool BKE_object_obdata_texspace_get(Object *ob, char **r_texflag, float **r_loc, float **r_size) { - if (ob->data == NULL) { + if (ob->data == nullptr) { return false; } @@ -4442,7 +4460,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; @@ -4456,7 +4474,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; } @@ -4474,25 +4492,52 @@ bool BKE_object_obdata_texspace_get(Object *ob, short **r_texflag, float **r_loc return true; } -/** Get evaluated mesh for given object. */ -Mesh *BKE_object_get_evaluated_mesh(const Object *object) +Mesh *BKE_object_get_evaluated_mesh_no_subsurf(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 in limited ways. Theoretically + * this should be avoided, or at least protected with a lock, so a const mesh could be returned + * from this function. We use a const_cast instead of #get_mesh_for_write, because that might + * result in a copy of the mesh when it is shared. */ + Mesh *mesh = const_cast<Mesh *>(geometry_set_eval->get_mesh_for_read()); + 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; +} + +Mesh *BKE_object_get_evaluated_mesh(const Object *object) +{ + Mesh *mesh = BKE_object_get_evaluated_mesh_no_subsurf(object); + if (!mesh) { + return nullptr; + } + + if (object->data && GS(((const ID *)object->data)->name) == ID_ME) { + mesh = BKE_mesh_wrapper_ensure_subdivision(object, mesh); + } + + return mesh; } -/** - * Get mesh which is not affected by modifiers: - * - For original objects it will be same as `object->data`, and it is a mesh - * which is in the corresponding #Main. - * - For copied-on-write objects it will give pointer to a copied-on-write - * mesh which corresponds to original object's mesh. - */ 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); @@ -4500,37 +4545,58 @@ 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; } -/** - * Get a mesh which corresponds to the very original mesh from #Main. - * - For original objects it will be object->data. - * - For evaluated objects it will be same mesh as corresponding original - * object uses as data. - */ 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; } +Mesh *BKE_object_get_editmesh_eval_final(const Object *object) +{ + BLI_assert(!DEG_is_original_id(&object->id)); + BLI_assert(object->type == OB_MESH); + + const Mesh *mesh = static_cast<const Mesh *>(object->data); + if (mesh->edit_mesh == nullptr) { + /* Happens when requesting material of evaluated 3d font object: the evaluated object get + * converted to mesh, and it does not have edit mesh. */ + return nullptr; + } + + return reinterpret_cast<Mesh *>(object->runtime.data_eval); +} + +Mesh *BKE_object_get_editmesh_eval_cage(const Object *object) +{ + BLI_assert(!DEG_is_original_id(&object->id)); + BLI_assert(object->type == OB_MESH); + + const Mesh *mesh = static_cast<const Mesh *>(object->data); + BLI_assert(mesh->edit_mesh != nullptr); + UNUSED_VARS_NDEBUG(mesh); + + return object->runtime.editmesh_eval_cage; +} + 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; @@ -4545,8 +4611,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; @@ -4557,9 +4623,15 @@ Lattice *BKE_object_get_evaluated_lattice(const Object *object) return lt_eval; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Point Cache + * \{ */ + 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; } @@ -4573,12 +4645,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) { @@ -4586,7 +4658,7 @@ int BKE_object_insert_ptcache(Object *ob) } } - link = MEM_callocN(sizeof(LinkData), "PCLink"); + link = MEM_cnew<LinkData>("PCLink"); link->data = POINTER_FROM_INT(i); BLI_addtail(&ob->pc_ids, link); @@ -4597,11 +4669,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; @@ -4617,10 +4689,12 @@ 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); } +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Object Data Shape Key Insert * \{ */ @@ -4628,12 +4702,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; @@ -4660,12 +4734,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; @@ -4698,13 +4772,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; @@ -4747,7 +4821,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: @@ -4765,7 +4839,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; } @@ -4779,12 +4853,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); @@ -4793,18 +4867,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; @@ -4818,20 +4891,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; } } @@ -4859,6 +4933,22 @@ bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Object Query API + * \{ */ + +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 == nullptr) { + return false; + } + if (ob == par) { + return true; + } + return BKE_object_parent_loop_check(par->parent, ob); +} + bool BKE_object_flag_test_recursive(const Object *ob, short flag) { if (ob->flag & flag) { @@ -4881,10 +4971,6 @@ bool BKE_object_is_child_recursive(const Object *ob_parent, const Object *ob_chi return false; } -/** - * Most important if this is modified it should _always_ return true, in certain - * cases false positives are hard to avoid (shape keys for example). - */ int BKE_object_is_modified(Scene *scene, Object *ob) { /* Always test on original object since evaluated object may no longer @@ -4918,21 +5004,6 @@ int BKE_object_is_modified(Scene *scene, Object *ob) return flag; } -/** - * Check of objects moves in time. - * - * \note This function is currently optimized for usage in combination - * with modifier deformation checks (#eModifierTypeType_OnlyDeform), - * so modifiers can quickly check if their target objects moves - * (causing deformation motion blur) or not. - * - * This makes it possible to give some degree of false-positives here, - * but it's currently an acceptable tradeoff between complexity and check - * speed. In combination with checks of modifier stack and real life usage - * percentage of false-positives shouldn't be that high. - * - * \note This function does not consider physics systems. - */ bool BKE_object_moves_in_time(const Object *object, bool recurse_parent) { /* If object has any sort of animation data assume it is moving. */ @@ -4942,7 +5013,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; @@ -4955,7 +5026,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)) { @@ -4972,19 +5043,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. @@ -5010,17 +5081,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; } @@ -5029,11 +5099,6 @@ static bool modifiers_has_animation_check(const Object *ob) return false; } -/** - * Test if object is affected by deforming modifiers (for motion blur). again - * most important is to avoid false positives, this is to skip computations - * and we can still if there was actual deformation afterwards. - */ int BKE_object_is_deform_modified(Scene *scene, Object *ob) { /* Always test on original object since evaluated object may no longer @@ -5051,7 +5116,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; } } @@ -5060,7 +5125,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) { @@ -5083,11 +5148,10 @@ int BKE_object_is_deform_modified(Scene *scene, Object *ob) return flag; } -/** Return the number of scenes using (instantiating) that object in their collections. */ 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++; } @@ -5097,12 +5161,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; } } @@ -5111,7 +5175,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; } @@ -5123,32 +5187,46 @@ MovieClip *BKE_object_movieclip_get(Scene *scene, Object *ob, bool use_default) return clip; } +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); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Runtime + * \{ */ + void BKE_object_runtime_reset(Object *object) { memset(&object->runtime, 0, sizeof(object->runtime)); } -/** - * Reset all pointers which we don't want to be shared when copying the 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; + + runtime->crazyspace_deform_imats = nullptr; + runtime->crazyspace_deform_cos = nullptr; } -/** - * The function frees memory used by the runtime data, but not the runtime field itself. - * - * All runtime data is cleared to ensure it's not used again, - * in keeping with other `_free_data(..)` functions. - */ void BKE_object_runtime_free_data(Object *object) { /* Currently this is all that's needed. */ @@ -5157,12 +5235,18 @@ void BKE_object_runtime_free_data(Object *object) BKE_object_runtime_reset(object); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Relationships + * \{ */ + /** * Find an associated armature 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; @@ -5190,36 +5274,27 @@ static void obrel_list_add(LinkNode **links, Object *ob) ob->id.tag |= LIB_TAG_DOIT; } -/** - * Iterates over all objects of the given scene layer. - * Depending on the #eObjectSet flag: - * collect either #OB_SET_ALL, #OB_SET_VISIBLE or #OB_SET_SELECTED objects. - * If #OB_SET_VISIBLE or#OB_SET_SELECTED are collected, - * then also add related objects according to the given \a includeFilter. - */ 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)) { @@ -5247,10 +5322,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)) { @@ -5278,13 +5351,10 @@ LinkNode *BKE_object_relational_superset(struct ViewLayer *view_layer, return links; } -/** - * return all groups this object is a part of, caller must free. - */ 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); } @@ -5294,41 +5364,38 @@ 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); } } -/** - * Return a KDTree_3d from the deformed object (in worldspace) - * - * \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. - */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object KD-Tree + * \{ */ + 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 : 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); @@ -5361,7 +5428,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; @@ -5370,7 +5437,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; @@ -5404,7 +5471,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; @@ -5427,6 +5494,12 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot) return tree; } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Modifier Utilities + * \{ */ + bool BKE_object_modifier_use_time(Scene *scene, Object *ob, ModifierData *md, @@ -5450,7 +5523,7 @@ bool BKE_object_modifier_use_time(Scene *scene, /* 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; } @@ -5463,7 +5536,7 @@ bool BKE_object_modifier_use_time(Scene *scene, * 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; } @@ -5486,7 +5559,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)); @@ -5496,7 +5568,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; } @@ -5504,7 +5576,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; } @@ -5524,7 +5596,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)); @@ -5534,7 +5605,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; } @@ -5542,7 +5613,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; } @@ -5558,10 +5629,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; @@ -5575,10 +5645,6 @@ static void object_cacheIgnoreClear(Object *ob, int state) BLI_freelistN(&pidlist); } -/** - * \note this function should eventually be replaced by depsgraph functionality. - * Avoid calling this in new code unless there is a very good reason for it! - */ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -5589,7 +5655,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; @@ -5613,36 +5678,35 @@ 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); } } } @@ -5669,13 +5733,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); @@ -5684,19 +5748,22 @@ bool BKE_object_modifier_update_subframe(Depsgraph *depsgraph, return false; } -/** - * Updates select_id of all objects in the given \a bmain. - */ 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; } } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Object Conversion + * \{ */ + Mesh *BKE_object_to_mesh(Depsgraph *depsgraph, Object *object, bool preserve_all_data_layers) { BKE_object_to_mesh_clear(object); @@ -5708,11 +5775,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) @@ -5726,11 +5793,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) @@ -5744,28 +5811,39 @@ 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); } } +SubsurfModifierData *BKE_object_get_last_subsurf_modifier(const Object *ob) +{ + ModifierData *md = (ModifierData *)(ob->modifiers.last); + + while (md) { + if (md->type == eModifierType_Subsurf) { + break; + } + + md = md->prev; + } + + return (SubsurfModifierData *)(md); +} + void BKE_object_replace_data_on_shallow_copy(Object *ob, ID *new_data) { ob->type = BKE_object_obdata_to_type(new_data); - ob->data = new_data; - ob->runtime.geometry_set_eval = NULL; - ob->runtime.data_eval = NULL; - if (ob->runtime.bb != NULL) { + ob->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 = NULL; + 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); -} +/** \} */ diff --git a/source/blender/blenkernel/intern/object_deform.c b/source/blender/blenkernel/intern/object_deform.c index 511f5d4ae66..fb4f4a14265 100644 --- a/source/blender/blenkernel/intern/object_deform.c +++ b/source/blender/blenkernel/intern/object_deform.c @@ -63,13 +63,6 @@ static Lattice *object_defgroup_lattice_get(ID *id) return (lt->editlatt) ? lt->editlatt->latt : lt; } -/** - * Update users of vgroups from this object, according to given map. - * - * Use it when you remove or reorder vgroups in the object. - * - * \param map: an array mapping old indices to new indices. - */ void BKE_object_defgroup_remap_update_users(Object *ob, const int *map) { ModifierData *md; @@ -106,15 +99,13 @@ void BKE_object_defgroup_remap_update_users(Object *ob, const int *map) } } } + /** \} */ /* -------------------------------------------------------------------- */ /** \name Group creation * \{ */ -/** - * Add a vgroup of given name to object. *Does not* handle MDeformVert data at all! - */ bDeformGroup *BKE_object_defgroup_add_name(Object *ob, const char *name) { bDeformGroup *defgroup; @@ -129,17 +120,11 @@ bDeformGroup *BKE_object_defgroup_add_name(Object *ob, const char *name) return defgroup; } -/** - * Add a vgroup of default name to object. *Does not* handle MDeformVert data at all! - */ bDeformGroup *BKE_object_defgroup_add(Object *ob) { return BKE_object_defgroup_add_name(ob, DATA_("Group")); } -/** - * Create MDeformVert data for given ID. Work in Object mode only. - */ MDeformVert *BKE_object_defgroup_data_create(ID *id) { if (GS(id->name) == ID_ME) { @@ -156,18 +141,13 @@ MDeformVert *BKE_object_defgroup_data_create(ID *id) return NULL; } + /** \} */ /* -------------------------------------------------------------------- */ /** \name Group clearing * \{ */ -/** - * Remove all verts (or only selected ones) from given vgroup. Work in Object and Edit modes. - * - * \param use_selection: Only operate on selection. - * \return True if any vertex was removed, false otherwise. - */ bool BKE_object_defgroup_clear(Object *ob, bDeformGroup *dg, const bool use_selection) { MDeformVert *dv; @@ -239,12 +219,6 @@ bool BKE_object_defgroup_clear(Object *ob, bDeformGroup *dg, const bool use_sele return changed; } -/** - * Remove all verts (or only selected ones) from all vgroups. Work in Object and Edit modes. - * - * \param use_selection: Only operate on selection. - * \return True if any vertex was removed, false otherwise. - */ bool BKE_object_defgroup_clear_all(Object *ob, const bool use_selection) { bDeformGroup *dg; @@ -260,6 +234,7 @@ bool BKE_object_defgroup_clear_all(Object *ob, const bool use_selection) return changed; } + /** \} */ /* -------------------------------------------------------------------- */ @@ -406,9 +381,6 @@ static void object_defgroup_remove_edit_mode(Object *ob, bDeformGroup *dg) object_defgroup_remove_common(ob, dg, def_nr); } -/** - * Remove given vgroup from object. Work in Object and Edit modes. - */ void BKE_object_defgroup_remove(Object *ob, bDeformGroup *defgroup) { if (ob->type == OB_GPENCIL) { @@ -426,10 +398,6 @@ void BKE_object_defgroup_remove(Object *ob, bDeformGroup *defgroup) } } -/** - * Remove all vgroups from object. Work in Object and Edit modes. - * When only_unlocked=true, locked vertex groups are not removed. - */ void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked) { ListBase *defbase = BKE_object_defgroup_list_mutable(ob); @@ -469,19 +437,11 @@ void BKE_object_defgroup_remove_all_ex(struct Object *ob, bool only_unlocked) } } -/** - * Remove all vgroups from object. Work in Object and Edit modes. - */ void BKE_object_defgroup_remove_all(struct Object *ob) { BKE_object_defgroup_remove_all_ex(ob, false); } -/** - * Compute mapping for vertex groups with matching name, -1 is used for no remapping. - * Returns null if no remapping is required. - * The returned array has to be freed. - */ 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); @@ -549,11 +509,6 @@ void BKE_object_defgroup_index_map_apply(MDeformVert *dvert, } } -/** - * Get MDeformVert vgroup data from given object. Should only be used in Object mode. - * - * \return True if the id type supports weights. - */ bool BKE_object_defgroup_array_get(ID *id, MDeformVert **dvert_arr, int *dvert_tot) { if (id) { @@ -579,14 +534,11 @@ bool BKE_object_defgroup_array_get(ID *id, MDeformVert **dvert_arr, int *dvert_t *dvert_tot = 0; return false; } + /** \} */ /* --- functions for getting vgroup aligned maps --- */ -/** - * gets the status of "flag" for each bDeformGroup - * 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; @@ -675,8 +627,6 @@ bool *BKE_object_defgroup_validmap_get(Object *ob, const int defbase_tot) return defgroup_validmap; } -/* Returns total selected vgroups, - * wpi.defbase_sel is assumed malloc'd, all values are set */ bool *BKE_object_defgroup_selected_get(Object *ob, int defbase_tot, int *r_dg_flags_sel_tot) { bool *dg_selection = MEM_mallocN(defbase_tot * sizeof(bool), __func__); @@ -708,11 +658,6 @@ bool *BKE_object_defgroup_selected_get(Object *ob, int defbase_tot, int *r_dg_fl return dg_selection; } -/** - * Checks if the lock relative mode is applicable. - * - * \return true if an unlocked deform group is active. - */ bool BKE_object_defgroup_check_lock_relative(const bool *lock_flags, const bool *validmap, int index) @@ -720,11 +665,6 @@ bool BKE_object_defgroup_check_lock_relative(const bool *lock_flags, return validmap && validmap[index] && !(lock_flags && lock_flags[index]); } -/** - * Additional check for whether the lock relative mode is applicable in multi-paint mode. - * - * \return true if none of the selected groups are locked. - */ bool BKE_object_defgroup_check_lock_relative_multi(int defbase_tot, const bool *lock_flags, const bool *selected, @@ -747,11 +687,6 @@ bool BKE_object_defgroup_check_lock_relative_multi(int defbase_tot, return true; } -/** - * Takes a pair of boolean masks of all locked and all deform groups, and computes - * a pair of masks for locked deform and unlocked deform groups. Output buffers may - * reuse the input ones. - */ void BKE_object_defgroup_split_locked_validmap( int defbase_tot, const bool *locked, const bool *deform, bool *r_locked, bool *r_unlocked) { @@ -774,11 +709,6 @@ void BKE_object_defgroup_split_locked_validmap( } } -/** - * Marks mirror vgroups in output and counts them. - * Output and counter assumed to be already initialized. - * Designed to be usable after BKE_object_defgroup_selected_get to extend selection to mirror. - */ void BKE_object_defgroup_mirror_selection(struct Object *ob, int defbase_tot, const bool *dg_selection, @@ -808,9 +738,6 @@ void BKE_object_defgroup_mirror_selection(struct Object *ob, } } -/** - * Return the subset type of the Vertex Group Selection - */ bool *BKE_object_defgroup_subset_from_select_type(Object *ob, eVGroupSelect subset_type, int *r_defgroup_tot, @@ -873,9 +800,6 @@ bool *BKE_object_defgroup_subset_from_select_type(Object *ob, return defgroup_validmap; } -/** - * store indices from the defgroup_validmap (faster lookups in some cases) - */ void BKE_object_defgroup_subset_to_index_array(const bool *defgroup_validmap, const int defgroup_tot, int *r_defgroup_subset_map) diff --git a/source/blender/blenkernel/intern/object_dupli.cc b/source/blender/blenkernel/intern/object_dupli.cc index 04739ec19d3..3082d6f25f3 100644 --- a/source/blender/blenkernel/intern/object_dupli.cc +++ b/source/blender/blenkernel/intern/object_dupli.cc @@ -31,9 +31,9 @@ #include "BLI_string_utf8.h" #include "BLI_array.hh" -#include "BLI_float3.hh" #include "BLI_float4x4.hh" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_rand.h" #include "BLI_span.hh" #include "BLI_vector.hh" @@ -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; @@ -149,7 +147,7 @@ static void init_context(DupliContext *r_ctx, /** * Create sub-context for recursive duplis. */ -static void copy_dupli_context( +static bool copy_dupli_context( DupliContext *r_ctx, const DupliContext *ctx, Object *ob, const float mat[4][4], int index) { *r_ctx = *ctx; @@ -168,7 +166,13 @@ static void copy_dupli_context( r_ctx->persistent_id[r_ctx->level] = index; ++r_ctx->level; + if (r_ctx->level == MAX_DUPLI_RECUR - 1) { + std::cerr << "Warning: Maximum instance recursion level reached.\n"; + return false; + } + r_ctx->gen = get_dupli_generator(r_ctx); + return true; } /** @@ -186,7 +190,7 @@ static DupliObject *make_dupli(const DupliContext *ctx, /* Add a #DupliObject instance to the result container. */ if (ctx->duplilist) { - dob = (DupliObject *)MEM_callocN(sizeof(DupliObject), "dupli object"); + dob = MEM_cnew<DupliObject>("dupli object"); BLI_addtail(ctx->duplilist, dob); } else { @@ -256,7 +260,9 @@ static void make_recursive_duplis(const DupliContext *ctx, /* Simple preventing of too deep nested collections with #MAX_DUPLI_RECUR. */ if (ctx->level < MAX_DUPLI_RECUR) { DupliContext rctx; - copy_dupli_context(&rctx, ctx, ob, space_mat, index); + if (!copy_dupli_context(&rctx, ctx, ob, space_mat, index)) { + return; + } if (rctx.gen) { ctx->instance_stack->append(ob); rctx.gen->make_duplis(&rctx); @@ -299,34 +305,41 @@ static void make_child_duplis(const DupliContext *ctx, FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (ctx->collection, ob, mode) { if ((ob != ctx->obedit) && is_child(ob, parent)) { DupliContext pctx; - copy_dupli_context(&pctx, ctx, ctx->object, nullptr, _base_id); - - /* Meta-balls have a different dupli handling. */ - if (ob->type != OB_MBALL) { - ob->flag |= OB_DONE; /* Doesn't render. */ + if (copy_dupli_context(&pctx, ctx, ctx->object, nullptr, _base_id)) { + /* Meta-balls have a different dupli handling. */ + if (ob->type != OB_MBALL) { + ob->flag |= OB_DONE; /* Doesn't render. */ + } + make_child_duplis_cb(&pctx, userdata, ob); } - make_child_duplis_cb(&pctx, userdata, ob); } } 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); + if (copy_dupli_context(&pctx, ctx, ctx->object, nullptr, persistent_dupli_id)) { + /* Meta-balls have a different dupli-handling. */ + if (ob->type != OB_MBALL) { + ob->flag |= OB_DONE; /* Doesn't render. */ + } - /* Meta-balls have a different dupli-handling. */ - if (ob->type != OB_MBALL) { - ob->flag |= OB_DONE; /* Doesn't render. */ + make_child_duplis_cb(&pctx, userdata, ob); } - - make_child_duplis_cb(&pctx, userdata, ob); } + persistent_dupli_id++; } + DEG_OBJECT_ITER_END; } } @@ -358,7 +371,7 @@ static const Mesh *mesh_data_from_duplicator_object(Object *ob, if (em != nullptr) { /* Note that this will only show deformation if #eModifierMode_OnCage is enabled. * We could change this but it matches 2.7x behavior. */ - me_eval = em->mesh_eval_cage; + me_eval = BKE_object_get_editmesh_eval_cage(ob); if ((me_eval == nullptr) || (me_eval->runtime.wrapper_type == ME_WRAPPER_TYPE_BMESH)) { EditMeshData *emd = me_eval ? me_eval->runtime.edit_data : nullptr; @@ -448,6 +461,7 @@ struct VertexDupliData_Mesh { int totvert; const MVert *mvert; + const float (*vert_normals)[3]; const float (*orco)[3]; }; @@ -545,12 +559,9 @@ static void make_child_duplis_verts_from_mesh(const DupliContext *ctx, float child_imat[4][4]; mul_m4_m4m4(child_imat, inst_ob->imat, ctx->object->obmat); - const MVert *mv = mvert; - for (int i = 0; i < totvert; i++, mv++) { - const float *co = mv->co; - float no[3]; - normal_short_to_float_v3(no, mv->no); - DupliObject *dob = vertex_dupli(vdd->params.ctx, inst_ob, child_imat, i, co, no, use_rotation); + for (int i = 0; i < totvert; i++) { + DupliObject *dob = vertex_dupli( + vdd->params.ctx, inst_ob, child_imat, i, mvert[i].co, vdd->vert_normals[i], use_rotation); if (vdd->orco) { copy_v3_v3(dob->orco, vdd->orco[i]); } @@ -627,6 +638,7 @@ static void make_duplis_verts(const DupliContext *ctx) vdd.params = vdd_params; vdd.totvert = me_eval->totvert; vdd.mvert = me_eval->mvert; + vdd.vert_normals = BKE_mesh_vertex_normals_ensure(me_eval); vdd.orco = (const float(*)[3])CustomData_get_layer(&me_eval->vdata, CD_ORCO); make_child_duplis(ctx, &vdd, make_child_duplis_verts_from_mesh); @@ -884,7 +896,9 @@ static void make_duplis_geometry_set_impl(const DupliContext *ctx, * 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); + if (!copy_dupli_context(&new_instances_ctx, ctx, ctx->object, nullptr, component_index)) { + return; + } instances_ctx = &new_instances_ctx; } @@ -919,7 +933,9 @@ static void make_duplis_geometry_set_impl(const DupliContext *ctx, mul_m4_m4_pre(collection_matrix, parent_transform); DupliContext sub_ctx; - copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id); + if (!copy_dupli_context(&sub_ctx, instances_ctx, instances_ctx->object, nullptr, id)) { + break; + } eEvaluationMode mode = DEG_get_mode(instances_ctx->depsgraph); int object_id = 0; @@ -942,8 +958,9 @@ static void make_duplis_geometry_set_impl(const DupliContext *ctx, 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); + if (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: { @@ -1008,6 +1025,8 @@ static void get_dupliface_transform_from_coords(Span<float3> coords, const float scale_fac, float r_mat[4][4]) { + using namespace blender::math; + /* Location. */ float3 location(0); for (const float3 &coord : coords) { @@ -1018,9 +1037,7 @@ static void get_dupliface_transform_from_coords(Span<float3> coords, /* Rotation. */ float quat[4]; - float3 f_no; - cross_poly_v3(f_no, (const float(*)[3])coords.data(), (uint)coords.size()); - f_no.normalize(); + float3 f_no = normalize(cross_poly(coords)); tri_to_quat_ex(quat, coords[0], coords[1], coords[2], f_no); /* Scale. */ @@ -1490,7 +1507,7 @@ static void make_duplis_particle_system(const DupliContext *ctx, ParticleSystem else { /* First key. */ state.time = ctime; - if (psys_get_particle_state(&sim, a, &state, 0) == 0) { + if (psys_get_particle_state(&sim, a, &state, false) == 0) { continue; } @@ -1600,8 +1617,9 @@ static void make_duplis_particles(const DupliContext *ctx) LISTBASE_FOREACH_INDEX (ParticleSystem *, psys, &ctx->object->particlesystem, psysid) { /* Particles create one more level for persistent `psys` index. */ DupliContext pctx; - copy_dupli_context(&pctx, ctx, ctx->object, nullptr, psysid); - make_duplis_particle_system(&pctx, psys); + if (copy_dupli_context(&pctx, ctx, ctx->object, nullptr, psysid)) { + make_duplis_particle_system(&pctx, psys); + } } } @@ -1631,6 +1649,14 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) return nullptr; } + /* Give "Object as Font" instances higher priority than geometry set instances, to retain + * the behavior from before curve object meshes were processed as instances internally. */ + if (transflag & OB_DUPLIVERTS) { + if (ctx->object->type == OB_FONT) { + return &gen_dupli_verts_font; + } + } + if (ctx->object->runtime.geometry_set_eval != nullptr) { if (BKE_object_has_geometry_set_instances(ctx->object)) { return &gen_dupli_geometry_set; @@ -1644,9 +1670,6 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) if (ctx->object->type == OB_MESH) { return &gen_dupli_verts; } - if (ctx->object->type == OB_FONT) { - return &gen_dupli_verts_font; - } if (ctx->object->type == OB_POINTCLOUD) { return &gen_dupli_verts_pointcloud; } @@ -1669,12 +1692,9 @@ static const DupliGenerator *get_dupli_generator(const DupliContext *ctx) /** \name Dupli-Container Implementation * \{ */ -/** - * \return a #ListBase of #DupliObject. - */ ListBase *object_duplilist(Depsgraph *depsgraph, Scene *sce, Object *ob) { - ListBase *duplilist = (ListBase *)MEM_callocN(sizeof(ListBase), "duplilist"); + ListBase *duplilist = MEM_cnew<ListBase>("duplilist"); DupliContext ctx; Vector<Object *> instance_stack; instance_stack.append(ob); diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c index 7e15ac5de5d..1a208355870 100644 --- a/source/blender/blenkernel/intern/object_update.c +++ b/source/blender/blenkernel/intern/object_update.c @@ -67,14 +67,6 @@ #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" -/** - * Restore the object->data to a non-modifier evaluated state. - * - * Some changes done directly in evaluated object require them to be reset - * before being re-evaluated. - * For example, we need to call this before #BKE_mesh_new_from_object(), - * in case we removed/added modifiers in the evaluated object. - */ void BKE_object_eval_reset(Object *ob_eval) { BKE_object_free_derived_caches(ob_eval); @@ -88,10 +80,10 @@ void BKE_object_eval_local_transform(Depsgraph *depsgraph, Object *ob) BKE_object_to_mat4(ob, ob->obmat); } -/* Evaluate parent */ -/* NOTE: based on solve_parenting(), but with the cruft stripped out */ void BKE_object_eval_parent(Depsgraph *depsgraph, Object *ob) { + /* NOTE: based on `solve_parenting()`, but with the cruft stripped out. */ + Object *par = ob->parent; float totmat[4][4]; @@ -168,12 +160,6 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o /* includes all keys and modifiers */ switch (ob->type) { case OB_MESH: { -#if 0 - BMEditMesh *em = (ob->mode & OB_MODE_EDIT) ? BKE_editmesh_from_object(ob) : NULL; -#else - BMEditMesh *em = (ob->mode & OB_MODE_EDIT) ? ((Mesh *)ob->data)->edit_mesh : NULL; -#endif - CustomData_MeshMasks cddata_masks = scene->customdata_mask; CustomData_MeshMasks_update(&cddata_masks, &CD_MASK_BAREMESH); /* Custom attributes should not be removed automatically. They might be used by the render @@ -183,6 +169,11 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o cddata_masks.fmask |= CD_MASK_PROP_ALL; cddata_masks.pmask |= CD_MASK_PROP_ALL; cddata_masks.lmask |= CD_MASK_PROP_ALL; + + /* Also copy over normal layers to avoid recomputation. */ + cddata_masks.pmask |= CD_MASK_NORMAL; + cddata_masks.vmask |= CD_MASK_NORMAL; + /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). * Due to Line Art implementation, edge marks should also be shown in viewport. */ #ifdef WITH_FREESTYLE @@ -195,12 +186,7 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o cddata_masks.lmask |= CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL; cddata_masks.vmask |= CD_MASK_ORCO | CD_MASK_PROP_COLOR; } - if (em) { - makeDerivedMesh(depsgraph, scene, ob, em, &cddata_masks); /* was CD_MASK_BAREMESH */ - } - else { - makeDerivedMesh(depsgraph, scene, ob, NULL, &cddata_masks); - } + makeDerivedMesh(depsgraph, scene, ob, &cddata_masks); /* was CD_MASK_BAREMESH */ break; } case OB_ARMATURE: @@ -282,7 +268,12 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o /** Bounding box from evaluated geometry. */ static void object_sync_boundbox_to_original(Object *object_orig, Object *object_eval) { - BoundBox *bb = BKE_object_boundbox_get(object_eval); + BoundBox *bb = object_eval->runtime.bb; + if (!bb || (bb->flag & BOUNDBOX_DIRTY)) { + BKE_object_boundbox_calc_from_evaluated_geometry(object_eval); + } + + bb = BKE_object_boundbox_get(object_eval); if (bb != NULL) { if (object_orig->runtime.bb == NULL) { object_orig->runtime.bb = MEM_mallocN(sizeof(*object_orig->runtime.bb), __func__); diff --git a/source/blender/blenkernel/intern/ocean.c b/source/blender/blenkernel/intern/ocean.c index e9683d3b52c..97326c24a61 100644 --- a/source/blender/blenkernel/intern/ocean.c +++ b/source/blender/blenkernel/intern/ocean.c @@ -270,7 +270,6 @@ void BKE_ocean_eval_uv(struct Ocean *oc, struct OceanResult *ocr, float u, float BLI_rw_mutex_unlock(&oc->oceanmutex); } -/* use catmullrom interpolation rather than linear */ void BKE_ocean_eval_uv_catrom(struct Ocean *oc, struct OceanResult *ocr, float u, float v) { int i0, i1, i2, i3, j0, j1, j2, j3; @@ -378,8 +377,6 @@ void BKE_ocean_eval_xz_catrom(struct Ocean *oc, struct OceanResult *ocr, float x BKE_ocean_eval_uv_catrom(oc, ocr, x / oc->_Lx, z / oc->_Lz); } -/* note that this doesn't wrap properly for i, j < 0, but its not really meant for that being - * just a way to get the raw data out to save in some image format. */ void BKE_ocean_eval_ij(struct Ocean *oc, struct OceanResult *ocr, int i, int j) { BLI_rw_mutex_lock(&oc->oceanmutex, THREAD_LOCK_READ); @@ -650,9 +647,6 @@ 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; @@ -777,9 +771,6 @@ bool BKE_ocean_ensure(struct OceanModifierData *omd, const int resolution) return true; } -/** - * 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) @@ -818,9 +809,6 @@ bool BKE_ocean_init_from_modifier(struct Ocean *ocean, 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, 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 c5504b22b43..43e0f399213 100644 --- a/source/blender/blenkernel/intern/ocean_spectrum.c +++ b/source/blender/blenkernel/intern/ocean_spectrum.c @@ -128,11 +128,6 @@ static float jonswap(const Ocean *oc, const float k2) return val; } -/** - * Pierson-Moskowitz model, 1964, assumes waves reach equilibrium with wind. - * Model is intended for large area 'fully developed' sea, where winds have been steadily blowing - * for days over an area that includes hundreds of wavelengths on a side. - */ float BLI_ocean_spectrum_piersonmoskowitz(const Ocean *oc, const float kx, const float kz) { const float k2 = kx * kx + kz * kz; @@ -159,10 +154,6 @@ float BLI_ocean_spectrum_piersonmoskowitz(const Ocean *oc, const float kx, const return val; } -/** - * TMA extends the JONSWAP spectrum. - * This spectral model is best suited to shallow water. - */ float BLI_ocean_spectrum_texelmarsenarsloe(const Ocean *oc, const float kx, const float kz) { const float k2 = kx * kx + kz * kz; @@ -195,14 +186,6 @@ float BLI_ocean_spectrum_texelmarsenarsloe(const Ocean *oc, const float kx, cons return val; } -/** - * Hasselmann et al, 1973. This model extends the Pierson-Moskowitz model with a peak sharpening - * function This enhancement is an artificial construct to address the problem that the wave - * spectrum is never fully developed. - * - * The fetch parameter represents the distance from a lee shore, - * called the fetch, or the distance over which the wind blows with constant velocity. - */ float BLI_ocean_spectrum_jonswap(const Ocean *oc, const float kx, const float kz) { const float k2 = kx * kx + kz * kz; diff --git a/source/blender/blenkernel/intern/packedFile.c b/source/blender/blenkernel/intern/packedFile.c index baff1bb47cc..3ddcdb424f9 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" @@ -242,7 +242,6 @@ PackedFile *BKE_packedfile_new(ReportList *reports, const char *filename, const return pf; } -/* no libraries for now */ void BKE_packedfile_pack_all(Main *bmain, ReportList *reports, bool verbose) { Image *ima; @@ -373,14 +372,6 @@ int BKE_packedfile_write_to_file(ReportList *reports, return ret_value; } -/** - * This function compares a packed file to a 'real' file. - * It returns an integer indicating if: - * - * - PF_EQUAL: the packed file and original file are identical - * - PF_DIFFERENT: the packed file and original file differ - * - PF_NOFILE: the original file doesn't exist - */ enum ePF_FileCompare BKE_packedfile_compare_to_file(const char *ref_file_name, const char *filename, PackedFile *pf) @@ -434,16 +425,6 @@ enum ePF_FileCompare BKE_packedfile_compare_to_file(const char *ref_file_name, return ret_val; } -/** - * #BKE_packedfile_unpack_to_file() looks at the existing files (abs_name, local_name) - * and a packed file. - * - * It returns a char *to the existing file name / new file name or NULL when - * there was an error or when the user decides to cancel the operation. - * - * \warning 'abs_name' may be relative still! (use a "//" prefix) - * be sure to run #BLI_path_abs on it first. - */ char *BKE_packedfile_unpack_to_file(ReportList *reports, const char *ref_file_name, const char *abs_name, @@ -753,7 +734,7 @@ void BKE_packedfile_pack_all_libraries(Main *bmain, ReportList *reports) { Library *lib; - /* test for relativenss */ + /* Test for relativeness. */ for (lib = bmain->libraries.first; lib; lib = lib->id.next) { if (!BLI_path_is_rel(lib->filepath)) { break; @@ -804,28 +785,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: @@ -834,7 +814,6 @@ bool BKE_packedfile_id_check(ID *id) return false; } -/* ID should be not NULL */ void BKE_packedfile_id_unpack(Main *bmain, ID *id, ReportList *reports, enum ePF_FileStatus how) { switch (GS(id->name)) { diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index d6030941c6d..407375c4d22 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -143,6 +143,7 @@ IDTypeInfo IDType_ID_PAL = { .name_plural = "palettes", .translation_context = BLT_I18NCONTEXT_ID_PALETTE, .flags = IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = palette_init_data, .copy_data = palette_copy_data, @@ -150,6 +151,7 @@ IDTypeInfo IDType_ID_PAL = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = palette_blend_write, @@ -208,6 +210,7 @@ IDTypeInfo IDType_ID_PC = { .name_plural = "paint_curves", .translation_context = BLT_I18NCONTEXT_ID_PAINTCURVE, .flags = IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = NULL, .copy_data = paint_curve_copy_data, @@ -215,6 +218,7 @@ IDTypeInfo IDType_ID_PC = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = paint_curve_blend_write, @@ -723,7 +727,6 @@ void BKE_paint_curve_clamp_endpoint_add_index(PaintCurve *pc, const int add_inde pc->add_index = (add_index || pc->tot_points == 1) ? (add_index + 1) : 0; } -/** Remove color from palette. Must be certain color is inside the palette! */ void BKE_palette_color_remove(Palette *palette, PaletteColor *color) { if (BLI_listbase_count_at_most(&palette->colors, palette->active_color) == @@ -962,7 +965,6 @@ bool BKE_palette_from_hash(Main *bmain, GHash *color_table, const char *name, co return done; } -/* are we in vertex paint or weight paint face select mode? */ bool BKE_paint_select_face_test(Object *ob) { return ((ob != NULL) && (ob->type == OB_MESH) && (ob->data != NULL) && @@ -970,7 +972,6 @@ bool BKE_paint_select_face_test(Object *ob) (ob->mode & (OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))); } -/* are we in weight paint vertex select mode? */ bool BKE_paint_select_vert_test(Object *ob) { return ((ob != NULL) && (ob->type == OB_MESH) && (ob->data != NULL) && @@ -978,10 +979,6 @@ bool BKE_paint_select_vert_test(Object *ob) (ob->mode & OB_MODE_WEIGHT_PAINT || ob->mode & OB_MODE_VERTEX_PAINT)); } -/** - * used to check if selection is possible - * (when we don't care if its face or vert) - */ bool BKE_paint_select_elem_test(Object *ob) { return (BKE_paint_select_vert_test(ob) || BKE_paint_select_face_test(ob)); @@ -1024,9 +1021,6 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode) } } -/** - * Call when entering each respective paint mode. - */ bool BKE_paint_ensure(ToolSettings *ts, struct Paint **r_paint) { Paint *paint = NULL; @@ -1147,10 +1141,6 @@ void BKE_paint_free(Paint *paint) MEM_SAFE_FREE(paint->tool_slots); } -/* called when copying scene settings, so even if 'src' and 'tar' are the same - * still do a id_us_plus(), rather than if we were copying between 2 existing - * scenes where a matching value should decrease the existing user count as - * with paint_brush_set() */ void BKE_paint_copy(Paint *src, Paint *tar, const int flag) { tar->brush = src->brush; @@ -1230,8 +1220,6 @@ void BKE_paint_blend_read_lib(BlendLibReader *reader, Scene *sce, Paint *p) } } -/* returns non-zero if any of the face's vertices - * are hidden, zero otherwise */ bool paint_is_face_hidden(const MLoopTri *lt, const MVert *mvert, const MLoop *mloop) { return ((mvert[mloop[lt->tri[0]].v].flag & ME_HIDE) || @@ -1239,9 +1227,6 @@ bool paint_is_face_hidden(const MLoopTri *lt, const MVert *mvert, const MLoop *m (mvert[mloop[lt->tri[2]].v].flag & ME_HIDE)); } -/* returns non-zero if any of the corners of the grid - * face whose inner corner is at (x, y) are hidden, - * zero otherwise */ bool paint_is_grid_face_hidden(const uint *grid_hidden, int gridsize, int x, int y) { /* skip face if any of its corners are hidden */ @@ -1251,7 +1236,6 @@ bool paint_is_grid_face_hidden(const uint *grid_hidden, int gridsize, int x, int BLI_BITMAP_TEST(grid_hidden, (y + 1) * gridsize + x)); } -/* Return true if all vertices in the face are visible, false otherwise */ bool paint_is_bmesh_face_hidden(BMFace *f) { BMLoop *l_iter; @@ -1520,8 +1504,6 @@ void BKE_sculptsession_free(Object *ob) } } -/* Sculpt mode handles multires differently from regular meshes, but only if - * it's the last modifier on the stack and it is not on the first level */ MultiresModifierData *BKE_sculpt_multires_active(Scene *scene, Object *ob) { Mesh *me = (Mesh *)ob->data; @@ -1666,6 +1648,7 @@ static void sculpt_update_object(Depsgraph *depsgraph, ss->totvert = me->totvert; ss->totpoly = me->totpoly; ss->totfaces = me->totpoly; + ss->vert_normals = BKE_mesh_vertex_normals_ensure(me); ss->mvert = me->mvert; ss->mpoly = me->mpoly; ss->mloop = me->mloop; @@ -1811,7 +1794,6 @@ void BKE_sculpt_color_layer_create_if_needed(struct Object *object) DEG_id_tag_update(&orig_me->id, ID_RECALC_GEOMETRY_ALL_MODES); } -/** \warning Expects a fully evaluated depsgraph. */ void BKE_sculpt_update_object_for_edit( Depsgraph *depsgraph, Object *ob_orig, bool need_pmap, bool need_mask, bool need_colors) { @@ -1941,10 +1923,6 @@ static bool check_sculpt_object_deformed(Object *object, const bool for_construc return deformed; } -/** - * Ensures that a Face Set data-layers exists. If it does not, it creates one respecting the - * visibility stored in the vertices of the mesh. If it does, it copies the visibility from the - * mesh to the Face Sets. */ void BKE_sculpt_face_sets_ensure_from_base_mesh_visibility(Mesh *mesh) { const int face_sets_default_visible_id = 1; @@ -2046,12 +2024,6 @@ void BKE_sculpt_sync_face_set_visibility(struct Mesh *mesh, struct SubdivCCG *su BKE_sculpt_sync_face_sets_visibility_to_grids(mesh, subdiv_ccg); } -/** - * Ensures we do have expected mesh data in original mesh for the sculpt mode. - * - * \note IDs are expected to be original ones here, and calling code should ensure it updates its - * depsgraph properly after calling this function if it needs up-to-date evaluated data. - */ void BKE_sculpt_ensure_orig_mesh_data(Scene *scene, Object *object) { Mesh *mesh = BKE_mesh_from_object(object); @@ -2223,8 +2195,6 @@ void BKE_sculpt_bvh_update_from_ccg(PBVH *pbvh, SubdivCCG *subdiv_ccg) subdiv_ccg->grid_hidden); } -/* Test if PBVH can be used directly for drawing, which is faster than - * drawing the mesh and all updates that come with it. */ bool BKE_sculptsession_use_pbvh_draw(const Object *ob, const View3D *v3d) { SculptSession *ss = ob->sculpt; diff --git a/source/blender/blenkernel/intern/paint_toolslots.c b/source/blender/blenkernel/intern/paint_toolslots.c index 0ea0173f8a3..bdb7b483997 100644 --- a/source/blender/blenkernel/intern/paint_toolslots.c +++ b/source/blender/blenkernel/intern/paint_toolslots.c @@ -140,10 +140,6 @@ void BKE_paint_toolslots_brush_update(Paint *paint) BKE_paint_toolslots_brush_update_ex(paint, paint->brush); } -/** - * Run this to ensure brush types are set for each slot on entering modes - * (for new scenes for example). - */ void BKE_paint_toolslots_brush_validate(Main *bmain, Paint *paint) { /* Clear slots with invalid slots or mode (unlikely but possible). */ diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c index 50b0fb1c9f5..4dba13ce4c2 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); } } @@ -499,6 +500,7 @@ IDTypeInfo IDType_ID_PA = { .name_plural = "particles", .translation_context = BLT_I18NCONTEXT_ID_PARTICLESETTINGS, .flags = 0, + .asset_type_info = NULL, .init_data = particle_settings_init, .copy_data = particle_settings_copy_data, @@ -506,6 +508,7 @@ IDTypeInfo IDType_ID_PA = { .make_local = NULL, .foreach_id = particle_settings_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = particle_settings_blend_write, @@ -553,7 +556,6 @@ static void get_cpa_texture(Mesh *mesh, int event, float cfra); -/* few helpers for countall etc. */ int count_particles(ParticleSystem *psys) { ParticleSettings *part = psys->part; @@ -643,7 +645,7 @@ static void psys_free_path_cache_buffers(ParticleCacheKey **cache, ListBase *buf /************************************************/ /* Getting stuff */ /************************************************/ -/* get object's active particle system safely */ + ParticleSystem *psys_get_current(Object *ob) { ParticleSystem *psys; @@ -911,9 +913,11 @@ int psys_uses_gravity(ParticleSimulationData *sim) return sim->scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY && sim->psys->part && sim->psys->part->effector_weights->global_gravity != 0.0f; } + /************************************************/ /* Freeing stuff */ /************************************************/ + static void fluid_free_settings(SPHFluidSettings *fluid) { if (fluid) { @@ -1053,7 +1057,6 @@ void psys_free_pdd(ParticleSystem *psys) psys->pdd->partsize = 0; } } -/* free everything */ void psys_free(Object *ob, ParticleSystem *psys) { if (psys) { @@ -1148,7 +1151,27 @@ void psys_copy_particles(ParticleSystem *psys_dst, ParticleSystem *psys_src) /* Copy particles and children. */ psys_dst->particles = MEM_dupallocN(psys_src->particles); psys_dst->child = MEM_dupallocN(psys_src->child); - if (psys_dst->part->type == PART_HAIR) { + + /* Ideally this should only be performed if `(psys_dst->part->type == PART_HAIR)`. + * + * But #ParticleData (`psys_dst`) is some sub-data of the #Object ID, while #ParticleSettings + * (`psys_dst->part`) is another ID. In case the particle settings is a linked ID that gets + * missing, it will be replaced (in readfile code) by a place-holder, which defaults to a + * `PART_EMITTER` type of particle settings. + * + * This leads to a situation where each particle of `psys_dst` still has a valid allocated `hair` + * data, which should still be preserved in case the missing particle settings ID becomes valid + * again. + * + * Furthermore, #free_hair() always frees `pa->hair` if it's not NULL, regardless of the + * particle type. So *not* copying here would cause a double free (or more), e.g. freeing the + * copy-on-write copy and the original data will crash Blender. + * In any case, sharing pointers between `psys_src` and `psys_dst` should be forbidden. + * + * So while we could in theory 'sanitize' the situation by setting `pa->hair` to NULL in the new + * copy (in case of non-`PART_HAIR` type), it is probably safer for now to systematically + * duplicate the `hair` data if available. */ + { ParticleData *pa; int p; for (p = 0, pa = psys_dst->particles; p < psys_dst->totpart; p++, pa++) { @@ -1181,6 +1204,7 @@ void psys_copy_particles(ParticleSystem *psys_dst, ParticleSystem *psys_src) /************************************************/ /* Interpolation */ /************************************************/ + static float interpolate_particle_value( float v1, float v2, float v3, float v4, const float w[4], int four) { @@ -1648,8 +1672,9 @@ static void interpolate_pathcache(ParticleCacheKey *first, float t, ParticleCach /************************************************/ /* Particles on a dm */ /************************************************/ -/* interpolate a location on a face based on face coordinates */ + void psys_interpolate_face(MVert *mvert, + const float (*vert_normals)[3], MFace *mface, MTFace *tface, float (*orcodata)[3], @@ -1671,13 +1696,13 @@ void psys_interpolate_face(MVert *mvert, v2 = mvert[mface->v2].co; v3 = mvert[mface->v3].co; - normal_short_to_float_v3(n1, mvert[mface->v1].no); - normal_short_to_float_v3(n2, mvert[mface->v2].no); - normal_short_to_float_v3(n3, mvert[mface->v3].no); + copy_v3_v3(n1, vert_normals[mface->v1]); + copy_v3_v3(n2, vert_normals[mface->v2]); + copy_v3_v3(n3, vert_normals[mface->v3]); if (mface->v4) { v4 = mvert[mface->v4].co; - normal_short_to_float_v3(n4, mvert[mface->v4].no); + copy_v3_v3(n4, vert_normals[mface->v4]); interp_v3_v3v3v3v3(vec, v1, v2, v3, v4, w); @@ -1880,18 +1905,6 @@ static void psys_origspace_to_w(OrigSpaceFace *osface, int quad, const float w[4 } } -/** - * Find the final derived mesh tessface for a particle, from its original tessface index. - * This is slow and can be optimized but only for many lookups. - * - * \param mesh_final: Final mesh, it may not have the same topology as original mesh. - * \param mesh_original: Original mesh, use for accessing #MPoly to #MFace mapping. - * \param findex_orig: The input tessface index. - * \param fw: Face weights (position of the particle inside the \a findex_orig tessface). - * \param poly_nodes: May be NULL, otherwise an array of linked list, - * one for each final \a mesh_final polygon, containing all its tessfaces indices. - * \return The \a mesh_final tessface index. - */ int psys_particle_dm_face_lookup(Mesh *mesh_final, Mesh *mesh_original, int findex_orig, @@ -2073,7 +2086,6 @@ static int psys_map_index_on_dm(Mesh *mesh, return 1; } -/* interprets particle data to get a point on a mesh in object space */ void psys_particle_on_dm(Mesh *mesh_final, int from, int index, @@ -2113,13 +2125,13 @@ void psys_particle_on_dm(Mesh *mesh_final, } orcodata = CustomData_get_layer(&mesh_final->vdata, CD_ORCO); + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh_final); if (from == PART_FROM_VERT) { copy_v3_v3(vec, mesh_final->mvert[mapindex].co); if (nor) { - normal_short_to_float_v3(nor, mesh_final->mvert[mapindex].no); - normalize_v3(nor); + copy_v3_v3(nor, vert_normals[mapindex]); } if (orco) { @@ -2150,7 +2162,8 @@ void psys_particle_on_dm(Mesh *mesh_final, } if (from == PART_FROM_VOLUME) { - psys_interpolate_face(mvert, mface, mtface, orcodata, mapfw, vec, tmpnor, utan, vtan, orco); + psys_interpolate_face( + mvert, vert_normals, mface, mtface, orcodata, mapfw, vec, tmpnor, utan, vtan, orco); if (nor) { copy_v3_v3(nor, tmpnor); } @@ -2162,7 +2175,8 @@ void psys_particle_on_dm(Mesh *mesh_final, add_v3_v3(vec, tmpnor); } else { - psys_interpolate_face(mvert, mface, mtface, orcodata, mapfw, vec, nor, utan, vtan, orco); + psys_interpolate_face( + mvert, vert_normals, mface, mtface, orcodata, mapfw, vec, nor, utan, vtan, orco); } } } @@ -2195,9 +2209,11 @@ ParticleSystemModifierData *psys_get_modifier(Object *ob, ParticleSystem *psys) } return NULL; } + /************************************************/ /* Particles on a shape */ /************************************************/ + /* ready for future use */ static void psys_particle_on_shape(int UNUSED(distr), int UNUSED(index), @@ -2226,6 +2242,7 @@ static void psys_particle_on_shape(int UNUSED(distr), copy_v3_v3(orco, zerovec); } } + /************************************************/ /* Particles on emitter */ /************************************************/ @@ -2300,6 +2317,7 @@ void psys_particle_on_emitter(ParticleSystemModifierData *psmd, psys_particle_on_shape(from, index, fuv, vec, nor, utan, vtan, orco); } } + /************************************************/ /* Path Cache */ /************************************************/ @@ -3252,11 +3270,6 @@ static void cache_key_incremental_rotation(ParticleCacheKey *key0, } } -/** - * Calculates paths ready for drawing/rendering - * - Useful for making use of opengl vertex arrays for super fast strand drawing. - * - Makes child strands possible and creates them too into the cache. - * - Cached path data is also used to determine cut position for the editmode tool. */ void psys_cache_paths(ParticleSimulationData *sim, float cfra, const bool use_render_params) { PARTICLE_PSMD; @@ -3739,9 +3752,11 @@ void psys_cache_edit_paths(Depsgraph *depsgraph, } } } + /************************************************/ /* Particle Key handling */ /************************************************/ + void copy_particle_key(ParticleKey *to, ParticleKey *from, int time) { if (time) { @@ -3901,6 +3916,7 @@ void psys_mat_hair_to_global( /************************************************/ /* ParticleSettings handling */ /************************************************/ + static ModifierData *object_add_or_copy_particle_system( Main *bmain, Scene *scene, Object *ob, const char *name, const ParticleSystem *psys_orig) { @@ -4437,9 +4453,11 @@ void psys_get_texture( CLAMP_WARP_PARTICLE_TEXTURE_POS(PAMAP_DAMP, ptex->damp); CLAMP_PARTICLE_TEXTURE_POS(PAMAP_LENGTH, ptex->length); } + /************************************************/ /* Particle State */ /************************************************/ + float psys_get_timestep(ParticleSimulationData *sim) { return 0.04f * sim->psys->part->timetweak; @@ -4565,7 +4583,6 @@ static void get_child_modifier_parameters(ParticleSettings *part, ctx->mesh, cpa_from, cpa_num, cpa_fuv, ctx->vg_twist); } } -/* gets hair (or keyed) particles state at the "path time" specified in state->time */ void psys_get_particle_on_path(ParticleSimulationData *sim, int p, ParticleKey *state, @@ -4619,11 +4636,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); @@ -4834,8 +4851,10 @@ void psys_get_particle_on_path(ParticleSimulationData *sim, } } } -/* gets particle's state at a time, returns 1 if particle exists and can be seen and 0 if not */ -int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *state, int always) +bool psys_get_particle_state(ParticleSimulationData *sim, + int p, + ParticleKey *state, + const bool always) { ParticleSystem *psys = sim->psys; ParticleSettings *part = psys->part; @@ -4850,12 +4869,12 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta if (p >= totpart) { if (!psys->totchild) { - return 0; + return false; } if (part->childtype == PART_CHILD_FACES) { if (!(psys->flag & PSYS_KEYED)) { - return 0; + return false; } cpa = psys->child + p - totpart; @@ -4865,7 +4884,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta if (!always) { if ((state->time < 0.0f && !(part->flag & PART_UNBORN)) || (state->time > 1.0f && !(part->flag & PART_DIED))) { - return 0; + return false; } } @@ -4873,7 +4892,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta (part->lifetime * psys_frand(psys, p + 24)); psys_get_particle_on_path(sim, p, state, 1); - return 1; + return true; } cpa = sim->psys->child + p - totpart; @@ -4887,7 +4906,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta if (!always) { if ((cfra < pa->time && (part->flag & PART_UNBORN) == 0) || (cfra >= pa->dietime && (part->flag & PART_DIED) == 0)) { - return 0; + return false; } } @@ -4897,7 +4916,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta if (sim->psys->flag & PSYS_KEYED) { state->time = -cfra; psys_get_particle_on_path(sim, p, state, 1); - return 1; + return true; } if (cpa) { @@ -4997,7 +5016,7 @@ int psys_get_particle_state(ParticleSimulationData *sim, int p, ParticleKey *sta } } - return 1; + return true; } void psys_get_dupli_texture(ParticleSystem *psys, diff --git a/source/blender/blenkernel/intern/particle_distribute.c b/source/blender/blenkernel/intern/particle_distribute.c index 863476c6638..ba3f99a2800 100644 --- a/source/blender/blenkernel/intern/particle_distribute.c +++ b/source/blender/blenkernel/intern/particle_distribute.c @@ -626,7 +626,8 @@ static void distribute_from_volume_exec(ParticleTask *thread, ParticleData *pa, /* experimental */ tot = mesh->totface; - psys_interpolate_face(mvert, mface, 0, 0, pa->fuv, co, nor, 0, 0, 0); + psys_interpolate_face( + mvert, BKE_mesh_vertex_normals_ensure(mesh), mface, 0, 0, pa->fuv, co, nor, 0, 0, 0); normalize_v3(nor); negate_v3(nor); @@ -997,12 +998,7 @@ static int psys_thread_context_init_distribute(ParticleThreadContext *ctx, BKE_mesh_tessface_ensure(mesh); /* we need orco for consistent distributions */ - if (!CustomData_has_layer(&mesh->vdata, CD_ORCO)) { - /* Orcos are stored in normalized 0..1 range by convention. */ - float(*orcodata)[3] = BKE_mesh_orco_verts_get(ob); - BKE_mesh_orco_verts_transform(mesh, orcodata, mesh->totvert, false); - CustomData_add_layer(&mesh->vdata, CD_ORCO, CD_ASSIGN, orcodata, mesh->totvert); - } + BKE_mesh_orco_ensure(ob, mesh); if (from == PART_FROM_VERT) { MVert *mv = mesh->mvert; diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c index 8986847a034..e489f9e2bac 100644 --- a/source/blender/blenkernel/intern/particle_system.c +++ b/source/blender/blenkernel/intern/particle_system.c @@ -452,7 +452,6 @@ void psys_calc_dmcache(Object *ob, Mesh *mesh_final, Mesh *mesh_original, Partic } } -/* threaded child particle distribution and path caching */ void psys_thread_context_init(ParticleThreadContext *ctx, ParticleSimulationData *sim) { memset(ctx, 0, sizeof(ParticleThreadContext)); @@ -591,7 +590,6 @@ static void init_particle_texture(ParticleSimulationData *sim, ParticleData *pa, } } -/* set particle parameters that don't change during particle's life */ void init_particle(ParticleSimulationData *sim, ParticleData *pa) { ParticleSettings *part = sim->psys->part; @@ -1066,7 +1064,6 @@ static void evaluate_emitter_anim(struct Depsgraph *depsgraph, BKE_object_where_is_calc_time(depsgraph, scene, ob, cfra); } -/* sets particle to the emitter surface with initial velocity & rotation */ void reset_particle(ParticleSimulationData *sim, ParticleData *pa, float dtime, float cfra) { ParticleSystem *psys = sim->psys; @@ -1157,9 +1154,11 @@ static void reset_all_particles(ParticleSimulationData *sim, float dtime, float reset_particle(sim, pa, dtime, cfra); } } + /************************************************/ /* Particle targets */ /************************************************/ + ParticleSystem *psys_get_target_system(Object *ob, ParticleTarget *pt) { ParticleSystem *psys = NULL; @@ -1180,10 +1179,11 @@ ParticleSystem *psys_get_target_system(Object *ob, ParticleTarget *pt) return psys; } + /************************************************/ /* Keyed particles */ /************************************************/ -/* Counts valid keyed targets */ + void psys_count_keyed_targets(ParticleSimulationData *sim) { ParticleSystem *psys = sim->psys, *kpsys; @@ -1288,6 +1288,7 @@ static void set_keyed_keys(ParticleSimulationData *sim) /************************************************/ /* Point Cache */ /************************************************/ + void psys_make_temp_pointcache(Object *ob, ParticleSystem *psys) { PointCache *cache = psys->pointcache; @@ -1325,6 +1326,7 @@ static void bvhtree_balance_isolated(void *userdata) /************************************************/ /* Effectors */ /************************************************/ + static void psys_update_particle_bvhtree(ParticleSystem *psys, float cfra) { if (psys) { @@ -2181,7 +2183,6 @@ void psys_sph_finalize(SPHData *sphdata) } } -/* Sample the density field at a point in space. */ void psys_sph_density(BVHTree *tree, SPHData *sphdata, float co[3], float vars[2]) { ParticleSystem **psys = sphdata->psys; @@ -2234,6 +2235,7 @@ static void sph_integrate(ParticleSimulationData *sim, /************************************************/ /* Basic physics */ /************************************************/ + typedef struct EfData { ParticleTexture ptex; ParticleSimulationData *sim; @@ -2787,7 +2789,6 @@ static int collision_sphere_to_verts(ParticleCollision *col, return hit != NULL; } -/* Callback for BVHTree near test */ void BKE_psys_collision_neartest_cb(void *userdata, int index, const BVHTreeRay *ray, @@ -3179,10 +3180,14 @@ static void collision_check(ParticleSimulationData *sim, int p, float dfra, floa } } } + /************************************************/ /* Hair */ /************************************************/ -/* check if path cache or children need updating and do it if needed */ + +/** + * Check if path cache or children need updating and do it if needed. + */ static void psys_update_path_cache(ParticleSimulationData *sim, float cfra, const bool use_render_params) @@ -4627,7 +4632,6 @@ static void system_step(ParticleSimulationData *sim, float cfra, const bool use_ } } -/* system type has changed so set sensible defaults and clear non applicable flags */ void psys_changed_type(Object *ob, ParticleSystem *psys) { ParticleSettings *part = psys->part; @@ -4765,8 +4769,6 @@ static void particle_settings_free_local(ParticleSettings *particle_settings) MEM_freeN(particle_settings); } -/* main particle update call, checks that things are ok on the large scale and - * then advances in to actual particle calculations depending on particle type */ void particle_system_update(struct Depsgraph *depsgraph, Scene *scene, Object *ob, diff --git a/source/blender/blenkernel/intern/pbvh.c b/source/blender/blenkernel/intern/pbvh.c index ca1fada8c76..1926bbcda02 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" @@ -32,7 +32,7 @@ #include "DNA_meshdata_types.h" #include "BKE_ccg.h" -#include "BKE_mesh.h" /* for BKE_mesh_calc_normals */ +#include "BKE_mesh.h" #include "BKE_paint.h" #include "BKE_pbvh.h" #include "BKE_subdiv_ccg.h" @@ -78,7 +78,6 @@ void BB_reset(BB *bb) bb->bmax[0] = bb->bmax[1] = bb->bmax[2] = -FLT_MAX; } -/* Expand the bounding box to include a new coordinate */ void BB_expand(BB *bb, const float co[3]) { for (int i = 0; i < 3; i++) { @@ -87,7 +86,6 @@ void BB_expand(BB *bb, const float co[3]) } } -/* Expand the bounding box to include another bounding box */ void BB_expand_with_bb(BB *bb, BB *bb2) { for (int i = 0; i < 3; i++) { @@ -96,7 +94,6 @@ void BB_expand_with_bb(BB *bb, BB *bb2) } } -/* Return 0, 1, or 2 to indicate the widest axis of the bounding box */ int BB_widest_axis(const BB *bb) { float dim[3]; @@ -351,7 +348,6 @@ static void update_vb(PBVH *pbvh, PBVHNode *node, BBC *prim_bbc, int offset, int node->orig_vb = node->vb; } -/* Returns the number of visible quads in the nodes' grids. */ int BKE_pbvh_count_grid_quads(BLI_bitmap **grid_hidden, const int *grid_indices, int totgrid, @@ -555,14 +551,8 @@ static void pbvh_build(PBVH *pbvh, BB *cb, BBC *prim_bbc, int totprim) build_sub(pbvh, 0, cb, prim_bbc, 0, totprim); } -/** - * Do a full rebuild with on Mesh data structure. - * - * \note Unlike mpoly/mloop/verts, looptri is **totally owned** by PBVH - * (which means it may rewrite it if needed, see #BKE_pbvh_vert_coords_apply(). - */ void BKE_pbvh_build_mesh(PBVH *pbvh, - const Mesh *mesh, + Mesh *mesh, const MPoly *mpoly, const MLoop *mloop, MVert *verts, @@ -582,6 +572,8 @@ void BKE_pbvh_build_mesh(PBVH *pbvh, pbvh->mloop = mloop; pbvh->looptri = looptri; pbvh->verts = verts; + BKE_mesh_vertex_normals_ensure(mesh); + pbvh->vert_normals = BKE_mesh_vertex_normals_for_write(mesh); pbvh->vert_bitmap = BLI_BITMAP_NEW(totvert, "bvh->vert_bitmap"); pbvh->totvert = totvert; pbvh->leaf_limit = LEAF_LIMIT; @@ -621,7 +613,6 @@ void BKE_pbvh_build_mesh(PBVH *pbvh, MEM_freeN(pbvh->vert_bitmap); } -/* Do a full rebuild with on Grids data structure */ void BKE_pbvh_build_grids(PBVH *pbvh, CCGElem **grids, int totgrid, @@ -1087,7 +1078,6 @@ static void pbvh_update_normals_store_task_cb(void *__restrict userdata, * so we know only this thread will handle this vertex. */ if (mvert->flag & ME_VERT_PBVH_UPDATE) { normalize_v3(vnors[v]); - normal_float_to_short_v3(mvert->no, vnors[v]); mvert->flag &= ~ME_VERT_PBVH_UPDATE; } } @@ -1098,10 +1088,6 @@ static void pbvh_update_normals_store_task_cb(void *__restrict userdata, static void pbvh_faces_update_normals(PBVH *pbvh, PBVHNode **nodes, int totnode) { - /* could be per node to save some memory, but also means - * we have to store for each vertex which node it is in */ - float(*vnors)[3] = MEM_callocN(sizeof(*vnors) * pbvh->totvert, __func__); - /* subtle assumptions: * - We know that for all edited vertices, the nodes with faces * adjacent to these vertices have been marked with PBVH_UpdateNormals. @@ -1115,7 +1101,7 @@ static void pbvh_faces_update_normals(PBVH *pbvh, PBVHNode **nodes, int totnode) PBVHUpdateData data = { .pbvh = pbvh, .nodes = nodes, - .vnors = vnors, + .vnors = pbvh->vert_normals, }; TaskParallelSettings settings; @@ -1123,8 +1109,6 @@ static void pbvh_faces_update_normals(PBVH *pbvh, PBVHNode **nodes, int totnode) BLI_task_parallel_range(0, totnode, &data, pbvh_update_normals_accum_task_cb, &settings); BLI_task_parallel_range(0, totnode, &data, pbvh_update_normals_store_task_cb, &settings); - - MEM_freeN(vnors); } static void pbvh_update_mask_redraw_task_cb(void *__restrict userdata, @@ -1311,6 +1295,7 @@ static void pbvh_update_draw_buffer_cb(void *__restrict userdata, case PBVH_FACES: GPU_pbvh_mesh_buffers_update(node->draw_buffers, pbvh->verts, + pbvh->vert_normals, CustomData_get_layer(pbvh->vdata, CD_PAINT_MASK), CustomData_get_layer(pbvh->ldata, CD_MLOOPCOL), CustomData_get_layer(pbvh->pdata, CD_SCULPT_FACE_SETS), @@ -1379,7 +1364,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); @@ -1954,11 +1939,6 @@ void BKE_pbvh_node_get_bm_orco_data(PBVHNode *node, *r_orco_coords = node->bm_orco; } -/** - * \note doing a full search on all vertices here seems expensive, - * however this is important to avoid having to recalculate bound-box & sync the buffers to the - * GPU (which is far more expensive!) See: T47232. - */ bool BKE_pbvh_node_vert_update_check_any(PBVH *pbvh, PBVHNode *node) { BLI_assert(pbvh->type == PBVH_FACES); @@ -1977,7 +1957,7 @@ bool BKE_pbvh_node_vert_update_check_any(PBVH *pbvh, PBVHNode *node) return false; } -/********************************* Raycast ***********************************/ +/********************************* Ray-cast ***********************************/ typedef struct { struct IsectRayAABB_Precalc ray; @@ -2798,7 +2778,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_msg(0, "PBVH: Given deforming vcos number does not natch PBVH vertex number!"); + BLI_assert_msg(0, "PBVH: Given deforming vcos number does not match PBVH vertex number!"); return; } @@ -2980,6 +2960,8 @@ void pbvh_vertex_iter_init(PBVH *pbvh, PBVHNode *node, PBVHVertexIter *vi, int m vi->mask = NULL; if (pbvh->type == PBVH_FACES) { + vi->vert_normals = pbvh->vert_normals; + vi->vmask = CustomData_get_layer(pbvh->vdata, CD_PAINT_MASK); vi->vcol = CustomData_get_layer(pbvh->vdata, CD_PROP_COLOR); } @@ -3053,6 +3035,12 @@ MVert *BKE_pbvh_get_verts(const PBVH *pbvh) return pbvh->verts; } +const float (*BKE_pbvh_get_vert_normals(const PBVH *pbvh))[3] +{ + BLI_assert(pbvh->type == PBVH_FACES); + return pbvh->vert_normals; +} + void BKE_pbvh_subdiv_cgg_set(PBVH *pbvh, SubdivCCG *subdiv_ccg) { pbvh->subdiv_ccg = subdiv_ccg; diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.c b/source/blender/blenkernel/intern/pbvh_bmesh.c index c30f94a4cf6..6f57448b0ab 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" @@ -1875,7 +1875,6 @@ static void pbvh_bmesh_create_nodes_fast_recursive( /***************************** Public API *****************************/ -/* Build a PBVH from a BMesh */ void BKE_pbvh_build_bmesh(PBVH *pbvh, BMesh *bm, bool smooth_shading, @@ -1953,7 +1952,6 @@ void BKE_pbvh_build_bmesh(PBVH *pbvh, MEM_freeN(nodeinfo); } -/* Collapse short edges, subdivide long edges */ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, PBVHTopologyUpdateMode mode, const float center[3], @@ -2031,10 +2029,6 @@ bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh, return modified; } -/* In order to perform operations on the original node coordinates - * (currently just raycast), store the node's triangles and vertices. - * - * Skips triangles that are hidden. */ void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, PBVHNode *node) { /* Skip if original coords/triangles are already saved */ diff --git a/source/blender/blenkernel/intern/pbvh_intern.h b/source/blender/blenkernel/intern/pbvh_intern.h index 84c4ae4dead..9562cda5f28 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 */ @@ -130,6 +130,9 @@ struct PBVH { /* Mesh data */ const struct Mesh *mesh; + + /* Note: Normals are not const because they can be updated for drawing by sculpt code. */ + float (*vert_normals)[3]; MVert *verts; const MPoly *mpoly; const MLoop *mloop; @@ -180,9 +183,18 @@ struct PBVH { /* pbvh.c */ void BB_reset(BB *bb); +/** + * Expand the bounding box to include a new coordinate. + */ void BB_expand(BB *bb, const float co[3]); +/** + * Expand the bounding box to include another bounding box. + */ void BB_expand_with_bb(BB *bb, BB *bb2); void BBC_update_centroid(BBC *bbc); +/** + * Return 0, 1, or 2 to indicate the widest axis of the bounding box. + */ int BB_widest_axis(const BB *bb); void pbvh_grow_nodes(PBVH *bvh, int totnode); bool ray_face_intersection_quad(const float ray_start[3], diff --git a/source/blender/blenkernel/intern/pointcache.c b/source/blender/blenkernel/intern/pointcache.c index 57225872c7e..602546db8df 100644 --- a/source/blender/blenkernel/intern/pointcache.c +++ b/source/blender/blenkernel/intern/pointcache.c @@ -266,7 +266,8 @@ static void ptcache_softbody_error(const ID *UNUSED(owner_id), /* ignored for now */ } -/* Particle functions */ +/* Particle functions. */ + void BKE_ptcache_make_particle_key(ParticleKey *key, int index, void **data, float time) { PTCACHE_DATA_TO(data, BPHYS_DATA_LOCATION, index, key->co); @@ -830,31 +831,23 @@ static void ptcache_rigidbody_interpolate(int index, RigidBodyOb *rbo = ob->rigidbody_object; if (rbo->type == RBO_TYPE_ACTIVE) { - ParticleKey keys[4]; - ParticleKey result; - float dfra; - - memset(keys, 0, sizeof(keys)); - - copy_v3_v3(keys[1].co, rbo->pos); - copy_qt_qt(keys[1].rot, rbo->orn); + /* It may be possible to improve results by taking into account velocity + * for interpolation using psys_interpolate_particle, however this is + * not currently cached. */ + float pos[3], orn[4]; if (old_data) { - memcpy(keys[2].co, data, sizeof(float[3])); - memcpy(keys[2].rot, data + 3, sizeof(float[4])); + memcpy(pos, data, sizeof(float[3])); + memcpy(orn, data + 3, sizeof(float[4])); } else { - BKE_ptcache_make_particle_key(&keys[2], 0, data, cfra2); + PTCACHE_DATA_TO(data, BPHYS_DATA_LOCATION, index, pos); + PTCACHE_DATA_TO(data, BPHYS_DATA_ROTATION, index, orn); } - dfra = cfra2 - cfra1; - - /* 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); - - copy_v3_v3(rbo->pos, result.co); - copy_qt_qt(rbo->orn, result.rot); + const float t = (cfra - cfra1) / (cfra2 - cfra1); + interp_v3_v3v3(rbo->pos, rbo->pos, pos, t); + interp_qt_qtqt(rbo->orn, rbo->orn, orn, t); } } } @@ -873,6 +866,7 @@ static void ptcache_rigidbody_error(const struct ID *UNUSED(owner_id), } /* Creating ID's */ + void BKE_ptcache_id_from_softbody(PTCacheID *pid, Object *ob, SoftBody *sb) { memset(pid, 0, sizeof(PTCacheID)); @@ -1008,9 +1002,6 @@ void BKE_ptcache_id_from_cloth(PTCacheID *pid, Object *ob, ClothModifierData *cl pid->file_type = PTCACHE_FILE_PTCACHE; } -/* The fluid modifier does not actually use this anymore, but some parts of Blender expect that it - * still has a point cache currently. For example, the fluid modifier uses - * #DEG_add_collision_relations, which internally creates relations with the point cache. */ void BKE_ptcache_id_from_smoke(PTCacheID *pid, struct Object *ob, struct FluidModifierData *fmd) { FluidDomainSettings *fds = fmd->domain; @@ -1104,10 +1095,6 @@ void BKE_ptcache_id_from_rigidbody(PTCacheID *pid, Object *ob, RigidBodyWorld *r pid->file_type = PTCACHE_FILE_PTCACHE; } -/** - * \param ob: Optional, may be NULL. - * \param scene: Optional may be NULL. - */ PTCacheID BKE_ptcache_id_find(Object *ob, Scene *scene, PointCache *cache) { PTCacheID result = {0}; @@ -1327,10 +1314,11 @@ static int ptcache_frame_from_filename(const char *filename, const char *ext) static int ptcache_path(PTCacheID *pid, char *filename) { + const char *blendfile_path = BKE_main_blendfile_path_from_global(); Library *lib = (pid->owner_id) ? pid->owner_id->lib : NULL; const char *blendfilename = (lib && (pid->cache->flag & PTCACHE_IGNORE_LIBPATH) == 0) ? lib->filepath_abs : - BKE_main_blendfile_path_from_global(); + blendfile_path; size_t i; if (pid->cache->flag & PTCACHE_EXTERNAL) { @@ -1342,7 +1330,7 @@ static int ptcache_path(PTCacheID *pid, char *filename) return BLI_path_slash_ensure(filename); /* new strlen() */ } - if (G.relbase_valid || lib) { + if ((blendfile_path[0] != '\0') || lib) { char file[MAX_PTCACHE_PATH]; /* we don't want the dir, only the file */ BLI_split_file_part(blendfilename, file, sizeof(file)); @@ -1427,8 +1415,11 @@ static int ptcache_filename(PTCacheID *pid, char *filename, int cfra, short do_p filename[0] = '\0'; newname = filename; - if (!G.relbase_valid && (pid->cache->flag & PTCACHE_EXTERNAL) == 0) { - return 0; /* save blend file before using disk pointcache */ + if ((pid->cache->flag & PTCACHE_EXTERNAL) == 0) { + const char *blendfile_path = BKE_main_blendfile_path_from_global(); + if (blendfile_path[0] == '\0') { + return 0; /* save blend file before using disk pointcache */ + } } /* start with temp dir */ @@ -1474,8 +1465,11 @@ static PTCacheFile *ptcache_file_open(PTCacheID *pid, int mode, int cfra) return NULL; } #endif - if (!G.relbase_valid && (pid->cache->flag & PTCACHE_EXTERNAL) == 0) { - return NULL; /* save blend file before using disk pointcache */ + if ((pid->cache->flag & PTCACHE_EXTERNAL) == 0) { + const char *blendfile_path = BKE_main_blendfile_path_from_global(); + if (blendfile_path[0] == '\0') { + return NULL; /* save blend file before using disk pointcache */ + } } ptcache_filename(pid, filename, cfra, 1, 1); @@ -1713,7 +1707,8 @@ static int ptcache_file_header_begin_write(PTCacheFile *pf) return 1; } -/* Data pointer handling */ +/* Data pointer handling. */ + int BKE_ptcache_data_size(int data_type) { return ptcache_data_size[data_type]; @@ -1734,7 +1729,6 @@ static void ptcache_file_pointers_init(PTCacheFile *pf) pf->cur[BPHYS_DATA_BOIDS] = (data_types & (1 << BPHYS_DATA_BOIDS)) ? &pf->data.boids : NULL; } -/* Check to see if point number "index" is in pm, uses binary search for index data. */ int BKE_ptcache_mem_index_find(PTCacheMem *pm, unsigned int index) { if (pm->totpoint > 0 && pm->data[BPHYS_DATA_INDEX]) { @@ -2288,7 +2282,6 @@ static int ptcache_interpolate(PTCacheID *pid, float cfra, int cfra1, int cfra2) return 1; } /* reads cache from disk or memory */ -/* possible to get old or interpolated result */ int BKE_ptcache_read(PTCacheID *pid, float cfra, bool no_extrapolate_old) { int cfrai = (int)floor(cfra), cfra1 = 0, cfra2 = 0; @@ -2549,7 +2542,6 @@ static int ptcache_write_needed(PTCacheID *pid, int cfra, int *overwrite) return 0; } -/* writes cache to disk or memory */ int BKE_ptcache_write(PTCacheID *pid, unsigned int cfra) { PointCache *cache = pid->cache; @@ -2600,7 +2592,8 @@ int BKE_ptcache_write(PTCacheID *pid, unsigned int cfra) * mode - PTCACHE_CLEAR_ALL, */ -/* Clears & resets */ +/* Clears & resets. */ + void BKE_ptcache_id_clear(PTCacheID *pid, int mode, unsigned int cfra) { unsigned int len; /* store the length of the string */ @@ -2632,8 +2625,6 @@ void BKE_ptcache_id_clear(PTCacheID *pid, int mode, unsigned int cfra) } #endif - // if (!G.relbase_valid) return; /* Save blend file before using pointcache. */ - /* clear all files in the temp dir with the prefix of the ID and the ".bphys" suffix */ switch (mode) { case PTCACHE_CLEAR_ALL: @@ -3014,50 +3005,6 @@ int BKE_ptcache_object_reset(Scene *scene, Object *ob, int mode) return reset; } -/* Use this when quitting blender, with unsaved files */ -void BKE_ptcache_remove(void) -{ - char path[MAX_PTCACHE_PATH]; - char path_full[MAX_PTCACHE_PATH]; - int rmdir = 1; - - ptcache_path(NULL, path); - - if (BLI_exists(path)) { - /* The pointcache dir exists? - remove all pointcache */ - - DIR *dir; - struct dirent *de; - - dir = opendir(path); - if (dir == NULL) { - return; - } - - while ((de = readdir(dir)) != NULL) { - if (FILENAME_IS_CURRPAR(de->d_name)) { - /* do nothing */ - } - else if (strstr(de->d_name, PTCACHE_EXT)) { /* Do we have the right extension? */ - BLI_join_dirfile(path_full, sizeof(path_full), path, de->d_name); - BLI_delete(path_full, false, false); - } - else { - rmdir = 0; /* unknown file, don't remove the dir */ - } - } - - closedir(dir); - } - else { - rmdir = 0; /* Path doesn't exist. */ - } - - if (rmdir) { - BLI_delete(path, true, false); - } -} - /* Point Cache handling */ PointCache *BKE_ptcache_add(ListBase *ptcaches) @@ -3150,7 +3097,6 @@ static PointCache *ptcache_copy(PointCache *cache, const bool copy_data) return ncache; } -/* returns first point cache */ PointCache *BKE_ptcache_copy_list(ListBase *ptcaches_new, const ListBase *ptcaches_old, const int flag) @@ -3170,6 +3116,7 @@ PointCache *BKE_ptcache_copy_list(ListBase *ptcaches_new, * every user action changing stuff, and then it runs a complete bake??? (ton) */ /* Baking */ + void BKE_ptcache_quick_cache_all(Main *bmain, Scene *scene, ViewLayer *view_layer) { PTCacheBaker baker; @@ -3202,7 +3149,6 @@ static void ptcache_dt_to_str(char *str, double dtime) } } -/* if bake is not given run simulations to current frame */ void BKE_ptcache_bake(PTCacheBaker *baker) { Scene *scene = baker->scene; @@ -3439,7 +3385,9 @@ void BKE_ptcache_bake(PTCacheBaker *baker) /* TODO: call redraw all windows somehow */ } + /* Helpers */ + void BKE_ptcache_disk_to_mem(PTCacheID *pid) { PointCache *cache = pid->cache; @@ -3495,8 +3443,9 @@ void BKE_ptcache_toggle_disk_cache(PTCacheID *pid) { PointCache *cache = pid->cache; int last_exact = cache->last_exact; + const char *blendfile_path = BKE_main_blendfile_path_from_global(); - if (!G.relbase_valid) { + if (blendfile_path[0] == '\0') { cache->flag &= ~PTCACHE_DISK_CACHE; if (G.debug & G_DEBUG) { printf("File must be saved before using disk cache!\n"); @@ -3548,6 +3497,11 @@ void BKE_ptcache_disk_cache_rename(PTCacheID *pid, const char *name_src, const c char old_path_full[MAX_PTCACHE_FILE]; char ext[MAX_PTCACHE_PATH]; + /* If both names are the same, there is nothing to do. */ + if (STREQ(name_src, name_dst)) { + return; + } + /* save old name */ BLI_strncpy(old_name, pid->cache->name, sizeof(old_name)); diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index 1db14dc3dc8..b5f016e4d76 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -25,10 +25,13 @@ #include "DNA_object_types.h" #include "DNA_pointcloud_types.h" +#include "BLI_index_range.hh" #include "BLI_listbase.h" -#include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_rand.h" +#include "BLI_span.hh" #include "BLI_string.h" +#include "BLI_task.hh" #include "BLI_utildefines.h" #include "BKE_anim_data.h" @@ -51,6 +54,10 @@ #include "BLO_read_write.h" +using blender::float3; +using blender::IndexRange; +using blender::Span; + /* PointCloud datablock */ static void pointcloud_random(PointCloud *pointcloud); @@ -79,7 +86,7 @@ static void pointcloud_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_s { PointCloud *pointcloud_dst = (PointCloud *)id_dst; const PointCloud *pointcloud_src = (const PointCloud *)id_src; - pointcloud_dst->mat = static_cast<Material **>(MEM_dupallocN(pointcloud_dst->mat)); + pointcloud_dst->mat = static_cast<Material **>(MEM_dupallocN(pointcloud_src->mat)); const eCDAllocType alloc_type = (flag & LIB_ID_COPY_CD_REFERENCE) ? CD_REFERENCE : CD_DUPLICATE; CustomData_copy(&pointcloud_src->pdata, @@ -105,7 +112,7 @@ 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); } } @@ -175,6 +182,7 @@ IDTypeInfo IDType_ID_PT = { /* name_plural */ "pointclouds", /* translation_context */ BLT_I18NCONTEXT_ID_POINTCLOUD, /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, /* init_data */ pointcloud_init_data, /* copy_data */ pointcloud_copy_data, @@ -182,6 +190,7 @@ IDTypeInfo IDType_ID_PT = { /* make_local */ nullptr, /* foreach_id */ pointcloud_foreach_id, /* foreach_cache */ nullptr, + /* foreach_path */ nullptr, /* owner_get */ nullptr, /* blend_write */ pointcloud_blend_write, @@ -259,18 +268,70 @@ PointCloud *BKE_pointcloud_new_nomain(const int totpoint) return pointcloud; } -void BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]) +struct MinMaxResult { + float3 min; + float3 max; +}; + +static MinMaxResult min_max_no_radii(Span<float3> positions) +{ + using namespace blender::math; + + return blender::threading::parallel_reduce( + positions.index_range(), + 1024, + MinMaxResult{float3(FLT_MAX), float3(-FLT_MAX)}, + [&](IndexRange range, const MinMaxResult &init) { + MinMaxResult result = init; + for (const int i : range) { + min_max(positions[i], result.min, result.max); + } + return result; + }, + [](const MinMaxResult &a, const MinMaxResult &b) { + return MinMaxResult{min(a.min, b.min), max(a.max, b.max)}; + }); +} + +static MinMaxResult min_max_with_radii(Span<float3> positions, Span<float> radii) { - float(*pointcloud_co)[3] = pointcloud->co; - float *pointcloud_radius = pointcloud->radius; - for (int a = 0; a < pointcloud->totpoint; a++) { - float *co = pointcloud_co[a]; - float radius = (pointcloud_radius) ? pointcloud_radius[a] : 0.0f; - const float co_min[3] = {co[0] - radius, co[1] - radius, co[2] - radius}; - const float co_max[3] = {co[0] + radius, co[1] + radius, co[2] + radius}; - DO_MIN(co_min, r_min); - DO_MAX(co_max, r_max); + using namespace blender::math; + + return blender::threading::parallel_reduce( + positions.index_range(), + 1024, + MinMaxResult{float3(FLT_MAX), float3(-FLT_MAX)}, + [&](IndexRange range, const MinMaxResult &init) { + MinMaxResult result = init; + for (const int i : range) { + result.min = min(positions[i] - radii[i], result.min); + result.max = max(positions[i] + radii[i], result.max); + } + return result; + }, + [](const MinMaxResult &a, const MinMaxResult &b) { + return MinMaxResult{min(a.min, b.min), max(a.max, b.max)}; + }); +} + +bool BKE_pointcloud_minmax(const PointCloud *pointcloud, float r_min[3], float r_max[3]) +{ + using namespace blender::math; + + if (!pointcloud->totpoint) { + return false; } + + Span<float3> positions{reinterpret_cast<float3 *>(pointcloud->co), pointcloud->totpoint}; + const MinMaxResult min_max = (pointcloud->radius) ? + min_max_with_radii(positions, + {pointcloud->radius, pointcloud->totpoint}) : + min_max_no_radii(positions); + + copy_v3_v3(r_min, min(min_max.min, float3(r_min))); + copy_v3_v3(r_max, max(min_max.max, float3(r_max))); + + return true; } BoundBox *BKE_pointcloud_boundbox_get(Object *ob) @@ -285,7 +346,7 @@ BoundBox *BKE_pointcloud_boundbox_get(Object *ob) ob->runtime.bb = static_cast<BoundBox *>(MEM_callocN(sizeof(BoundBox), "pointcloud boundbox")); } - blender::float3 min, max; + float3 min, max; INIT_MINMAX(min, max); if (ob->runtime.geometry_set_eval != nullptr) { ob->runtime.geometry_set_eval->compute_boundbox_without_instances(&min, &max); @@ -417,6 +478,7 @@ void BKE_pointcloud_data_update(struct Depsgraph *depsgraph, struct Scene *scene } /* Draw Cache */ + void (*BKE_pointcloud_batch_cache_dirty_tag_cb)(PointCloud *pointcloud, int mode) = nullptr; void (*BKE_pointcloud_batch_cache_free_cb)(PointCloud *pointcloud) = nullptr; diff --git a/source/blender/blenkernel/intern/preferences.c b/source/blender/blenkernel/intern/preferences.c index 0b8e8d7c311..4bb2231bbb1 100644 --- a/source/blender/blenkernel/intern/preferences.c +++ b/source/blender/blenkernel/intern/preferences.c @@ -24,6 +24,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_path_util.h" #include "BLI_string.h" @@ -61,10 +62,6 @@ 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); @@ -83,6 +80,14 @@ void BKE_preferences_asset_library_name_set(UserDef *userdef, sizeof(library->name)); } +void BKE_preferences_asset_library_path_set(bUserAssetLibrary *library, const char *path) +{ + BLI_strncpy(library->path, path, sizeof(library->path)); + if (BLI_is_file(library->path)) { + BLI_path_parent_dir(library->path); + } +} + bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index) { return BLI_findlink(&userdef->asset_libraries, index); @@ -120,7 +125,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..bc11861f2c8 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: @@ -76,11 +76,6 @@ void BKE_reports_init(ReportList *reports, int flag) reports->flag = flag; } -/** - * Only frees the list \a reports. - * To make displayed reports disappear, either remove window-manager reports - * (wmWindowManager.reports, or CTX_wm_reports()), or use #WM_report_banners_cancel(). - */ void BKE_reports_clear(ReportList *reports) { Report *report, *report_next; @@ -101,7 +96,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 +124,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 +210,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 +219,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 +228,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 +237,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 +246,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 +274,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 +300,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 328c54fc21b..75e9bc2fbee 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 */ @@ -103,7 +103,6 @@ static void RB_constraint_delete(void *UNUSED(con)) #endif -/* Free rigidbody world */ void BKE_rigidbody_free_world(Scene *scene) { bool is_orig = (scene->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0; @@ -160,7 +159,6 @@ void BKE_rigidbody_free_world(Scene *scene) MEM_freeN(rbw); } -/* Free RigidBody settings and sim instances */ void BKE_rigidbody_free_object(Object *ob, RigidBodyWorld *rbw) { bool is_orig = (ob->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0; @@ -208,7 +206,6 @@ void BKE_rigidbody_free_object(Object *ob, RigidBodyWorld *rbw) ob->rigidbody_object = NULL; } -/* Free RigidBody constraint and sim instance */ void BKE_rigidbody_free_constraint(Object *ob) { RigidBodyCon *rbc = (ob) ? ob->rigidbody_constraint : NULL; @@ -302,7 +299,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; } @@ -637,8 +634,6 @@ static void rigidbody_validate_sim_shape(RigidBodyWorld *rbw, Object *ob, bool r /* --------------------- */ -/* helper function to calculate volume of rigidbody object */ -/* TODO: allow a parameter to specify method used to calculate this? */ void BKE_rigidbody_calc_volume(Object *ob, float *r_vol) { RigidBodyOb *rbo = ob->rigidbody_object; @@ -1133,12 +1128,6 @@ static void rigidbody_validate_sim_constraint(RigidBodyWorld *rbw, Object *ob, b /* --------------------- */ -/** - * Create physics sim world given RigidBody world settings - * - * \note this does NOT update object references that the scene uses, - * in case those aren't ready yet! - */ void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, bool rebuild) { /* sanity checks */ @@ -1161,7 +1150,6 @@ void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, bool re /* ************************************** */ /* Setup Utilities - Create Settings Blocks */ -/* Set up RigidBody world */ RigidBodyWorld *BKE_rigidbody_create_world(Scene *scene) { /* try to get whatever RigidBody world that might be representing this already */ @@ -1211,8 +1199,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; @@ -1246,7 +1234,6 @@ void BKE_rigidbody_world_id_loop(RigidBodyWorld *rbw, RigidbodyWorldIDFunc func, } } -/* Add rigid body settings to the specified object */ RigidBodyOb *BKE_rigidbody_create_object(Scene *scene, Object *ob, short type) { RigidBodyOb *rbo; @@ -1309,7 +1296,6 @@ RigidBodyOb *BKE_rigidbody_create_object(Scene *scene, Object *ob, short type) return rbo; } -/* Add rigid body constraint to the specified object */ RigidBodyCon *BKE_rigidbody_create_constraint(Scene *scene, Object *ob, short type) { RigidBodyCon *rbc; @@ -1429,11 +1415,6 @@ void BKE_rigidbody_main_collection_object_add(Main *bmain, Collection *collectio /* ************************************** */ /* Utilities API */ -/** - * Get RigidBody world for the given scene, creating one if needed - * - * \param scene: Scene to find active Rigid Body world for. - */ RigidBodyWorld *BKE_rigidbody_get_world(Scene *scene) { /* sanity check */ @@ -1473,16 +1454,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); + } } } } @@ -2023,7 +2039,6 @@ bool BKE_rigidbody_check_sim_running(RigidBodyWorld *rbw, float ctime) return (rbw && (rbw->flag & RBW_FLAG_MUTED) == 0 && ctime > rbw->shared->pointcache->startframe); } -/* Sync rigid body and object transformations */ void BKE_rigidbody_sync_transforms(RigidBodyWorld *rbw, Object *ob, float ctime) { if (!BKE_rigidbody_is_affected_by_simulation(ob)) { @@ -2054,7 +2069,6 @@ void BKE_rigidbody_sync_transforms(RigidBodyWorld *rbw, Object *ob, float ctime) } } -/* Used when canceling transforms - return rigidbody and object to initial states */ void BKE_rigidbody_aftertrans_update( Object *ob, float loc[3], float rot[3], float quat[4], float rotAxis[3], float rotAngle) { @@ -2133,8 +2147,6 @@ void BKE_rigidbody_cache_reset(RigidBodyWorld *rbw) /* ------------------ */ -/* Rebuild rigid body world */ -/* NOTE: this needs to be called before frame update to work correctly */ void BKE_rigidbody_rebuild_world(Depsgraph *depsgraph, Scene *scene, float ctime) { RigidBodyWorld *rbw = scene->rigidbody_world; @@ -2174,7 +2186,6 @@ void BKE_rigidbody_rebuild_world(Depsgraph *depsgraph, Scene *scene, float ctime } } -/* Run RigidBody simulation for the specified physics world */ void BKE_rigidbody_do_simulation(Depsgraph *depsgraph, Scene *scene, float ctime) { RigidBodyWorld *rbw = scene->rigidbody_world; @@ -2272,6 +2283,7 @@ void BKE_rigidbody_object_copy(Main *bmain, Object *ob_dst, const Object *ob_src void BKE_rigidbody_validate_sim_world(Scene *scene, RigidBodyWorld *rbw, bool rebuild) { } + void BKE_rigidbody_calc_volume(Object *ob, float *r_vol) { if (r_vol) { diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c index 03f19cef94e..916a2786a98 100644 --- a/source/blender/blenkernel/intern/scene.c +++ b/source/blender/blenkernel/intern/scene.c @@ -71,6 +71,7 @@ #include "BKE_anim_data.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_bpath.h" #include "BKE_cachefile.h" #include "BKE_collection.h" #include "BKE_colortools.h" @@ -239,7 +240,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) @@ -471,7 +472,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)); } /** @@ -522,7 +524,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) { \ @@ -530,7 +535,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 @@ -541,13 +560,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 @@ -559,21 +578,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, @@ -582,110 +601,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) { @@ -695,7 +756,7 @@ 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); } } @@ -704,32 +765,33 @@ static bool seq_foreach_member_id_cb(Sequence *seq, void *user_data) { LibraryForeachIDData *data = (LibraryForeachIDData *)user_data; -#define FOREACHID_PROCESS(_data, _id_super, _cb_flag) \ +#define 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 false; \ } \ } \ ((void)0) - FOREACHID_PROCESS(data, seq->scene, IDWALK_CB_NEVER_SELF); - FOREACHID_PROCESS(data, seq->scene_camera, IDWALK_CB_NOP); - FOREACHID_PROCESS(data, seq->clip, IDWALK_CB_USER); - FOREACHID_PROCESS(data, seq->mask, IDWALK_CB_USER); - FOREACHID_PROCESS(data, seq->sound, IDWALK_CB_USER); + 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(data, smd->mask_id, IDWALK_CB_USER); + 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(data, text_data->text_font, IDWALK_CB_USER); + FOREACHID_PROCESS_IDSUPER(data, text_data->text_font, IDWALK_CB_USER); } -#undef FOREACHID_PROCESS +#undef FOREACHID_PROCESS_IDSUPER return true; } @@ -738,66 +800,77 @@ 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) { - SEQ_for_each_callback(&scene->ed->seqbase, seq_foreach_member_id_cb, data); + 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)); } } @@ -819,6 +892,45 @@ static void scene_foreach_cache(ID *id, user_data); } +static bool seq_foreach_path_callback(Sequence *seq, void *user_data) +{ + if (SEQ_HAS_PATH(seq)) { + StripElem *se = seq->strip->stripdata; + BPathForeachPathData *bpath_data = (BPathForeachPathData *)user_data; + + if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM) && se) { + BKE_bpath_foreach_path_dirfile_fixed_process(bpath_data, seq->strip->dir, se->name); + } + else if ((seq->type == SEQ_TYPE_IMAGE) && se) { + /* NOTE: An option not to loop over all strips could be useful? */ + unsigned int len = (unsigned int)MEM_allocN_len(se) / (unsigned int)sizeof(*se); + unsigned int i; + + if (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE) { + /* only operate on one path */ + len = MIN2(1u, len); + } + + for (i = 0; i < len; i++, se++) { + BKE_bpath_foreach_path_dirfile_fixed_process(bpath_data, seq->strip->dir, se->name); + } + } + else { + /* simple case */ + BKE_bpath_foreach_path_fixed_process(bpath_data, seq->strip->dir); + } + } + return true; +} + +static void scene_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Scene *scene = (Scene *)id; + if (scene->ed != NULL) { + SEQ_for_each_callback(&scene->ed->seqbase, seq_foreach_path_callback, bpath_data); + } +} + static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Scene *sce = (Scene *)id; @@ -999,7 +1111,7 @@ static void link_recurs_seq(BlendDataReader *reader, ListBase *lb) /* Sanity check. */ if (!SEQ_valid_strip_channel(seq)) { BLI_freelinkN(lb, seq); - BLO_read_data_reports(reader)->count.vse_strips_skipped++; + BLO_read_data_reports(reader)->count.sequence_strips_skipped++; } else if (seq->seqbase.first) { link_recurs_seq(reader, &seq->seqbase); @@ -1528,6 +1640,7 @@ IDTypeInfo IDType_ID_SCE = { .name_plural = "scenes", .translation_context = BLT_I18NCONTEXT_ID_SCENE, .flags = 0, + .asset_type_info = NULL, .init_data = scene_init_data, .copy_data = scene_copy_data, @@ -1537,6 +1650,7 @@ IDTypeInfo IDType_ID_SCE = { .make_local = NULL, .foreach_id = scene_foreach_id, .foreach_cache = scene_foreach_cache, + .foreach_path = scene_foreach_path, .owner_get = NULL, .blend_write = scene_blend_write, @@ -1587,7 +1701,6 @@ static void remove_sequencer_fcurves(Scene *sce) } } -/* flag -- copying options (see BKE_lib_id.h's LIB_ID_COPY_... flags for more). */ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag) { if (toolsettings == NULL) { @@ -1801,6 +1914,7 @@ Scene *BKE_scene_duplicate(Main *bmain, Scene *sce, eSceneCopyMethod type) /* 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); @@ -1816,24 +1930,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 @@ -1894,9 +2027,6 @@ Scene *BKE_scene_add(Main *bmain, const char *name) return sce; } -/** - * Check if there is any instance of the object in the scene - */ bool BKE_scene_object_find(Scene *scene, Object *ob) { LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) { @@ -1919,12 +2049,6 @@ Object *BKE_scene_object_find_by_name(const Scene *scene, const char *name) return NULL; } -/** - * Sets the active scene, mainly used when running in background mode - * (`--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. - */ void BKE_scene_set_background(Main *bmain, Scene *scene) { Object *ob; @@ -1949,7 +2073,6 @@ void BKE_scene_set_background(Main *bmain, Scene *scene) * (render code calls own animation updates). */ } -/* called from creator_args.c */ Scene *BKE_scene_set_name(Main *bmain, const char *name) { Scene *sce = (Scene *)BKE_libblock_find_name(bmain, ID_SCE, name); @@ -1963,8 +2086,6 @@ Scene *BKE_scene_set_name(Main *bmain, const char *name) return NULL; } -/* Used by meta-balls, return *all* objects (including duplis) - * existing in the scene (including scene's sets). */ int BKE_scene_base_iter_next( Depsgraph *depsgraph, SceneBaseIter *iter, Scene **scene, int val, Base **base, Object **ob) { @@ -2189,8 +2310,6 @@ const char *BKE_scene_find_marker_name(const Scene *scene, int frame) return NULL; } -/* return the current marker for this frame, - * we can have more than 1 marker per frame, this just returns the first :/ */ const char *BKE_scene_find_last_marker_name(const Scene *scene, int frame) { const TimeMarker *marker, *best_marker = NULL; @@ -2234,7 +2353,6 @@ void BKE_scene_remove_rigidbody_object(struct Main *bmain, } } -/* checks for cycle, returns 1 if it's all OK */ bool BKE_scene_validate_setscene(Main *bmain, Scene *sce) { Scene *sce_iter; @@ -2257,16 +2375,11 @@ bool BKE_scene_validate_setscene(Main *bmain, Scene *sce) return true; } -/* 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); } -/* 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; @@ -2276,13 +2389,11 @@ float BKE_scene_frame_to_ctime(const Scene *scene, const int frame) return ctime; } -/* 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; @@ -2319,12 +2430,6 @@ TransformOrientationSlot *BKE_scene_orientation_slot_get_from_flag(Scene *scene, return BKE_scene_orientation_slot_get(scene, slot_index); } -/** - * Activate a transform orientation in a 3D view based on an enum value. - * - * \param orientation: If this is #V3D_ORIENT_CUSTOM or greater, the custom transform orientation - * with index \a orientation - #V3D_ORIENT_CUSTOM gets activated. - */ void BKE_scene_orientation_slot_set_index(TransformOrientationSlot *orient_slot, int orientation) { const bool is_custom = orientation >= V3D_ORIENT_CUSTOM; @@ -2531,7 +2636,6 @@ void BKE_scene_graph_evaluated_ensure(Depsgraph *depsgraph, Main *bmain) scene_graph_update_tagged(depsgraph, bmain, true); } -/* applies changes right away, does all sets too */ void BKE_scene_graph_update_for_newframe_ex(Depsgraph *depsgraph, const bool clear_recalc) { Scene *scene = DEG_get_input_scene(depsgraph); @@ -2607,12 +2711,6 @@ void BKE_scene_graph_update_for_newframe(Depsgraph *depsgraph) BKE_scene_graph_update_for_newframe_ex(depsgraph, true); } -/** - * Ensures given scene/view_layer pair has a valid, up-to-date depsgraph. - * - * \warning Sets matching depsgraph as active, - * so should only be called from the active editing context (usually, from operators). - */ void BKE_scene_view_layer_graph_evaluated_ensure(Main *bmain, Scene *scene, ViewLayer *view_layer) { Depsgraph *depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, view_layer); @@ -2620,7 +2718,6 @@ void BKE_scene_view_layer_graph_evaluated_ensure(Main *bmain, Scene *scene, View BKE_scene_graph_update_tagged(depsgraph, bmain); } -/* return default view */ SceneRenderView *BKE_scene_add_render_view(Scene *sce, const char *name) { SceneRenderView *srv; @@ -2690,12 +2787,6 @@ int get_render_child_particle_number(const RenderData *r, int num, bool for_rend return num; } -/** - * Helper function for the SETLOOPER and SETLOOPER_VIEW_LAYER macros - * - * It iterates over the bases of the active layer and then the bases - * of the active layer of the background (set) scenes recursively. - */ Base *_setlooper_base_step(Scene **sce_iter, ViewLayer *view_layer, Base *base) { if (base && base->next) { @@ -2760,13 +2851,18 @@ typedef enum eCyclesFeatureSet { 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; } @@ -2780,16 +2876,6 @@ void BKE_scene_base_flag_to_objects(ViewLayer *view_layer) } } -/** - * Synchronize object base flags - * - * This is usually handled by the depsgraph. - * However, in rare occasions we need to use the latest object flags - * before depsgraph is fully updated. - * - * It should (ideally) only run for copy-on-written objects since this is - * runtime data generated per-viewlayer. - */ void BKE_scene_object_base_flag_sync_from_base(Base *base) { Object *ob = base->object; @@ -2862,10 +2948,6 @@ int BKE_render_preview_pixel_size(const RenderData *r) return r->preview_pixel_size; } -/** - * Apply the needed correction factor to value, based on unit_type - * (only length-related are affected currently) and unit->scale_length. - */ double BKE_scene_unit_scale(const UnitSettings *unit, const int unit_type, double value) { if (unit->system == USER_UNIT_NONE) { @@ -2940,7 +3022,6 @@ bool BKE_scene_multiview_is_stereo3d(const RenderData *rd) ((srv[1]->viewflag & SCE_VIEW_DISABLE) == 0)); } -/* return whether to render this SceneRenderView */ bool BKE_scene_multiview_is_render_view_active(const RenderData *rd, const SceneRenderView *srv) { if (srv == NULL) { @@ -2967,7 +3048,6 @@ bool BKE_scene_multiview_is_render_view_active(const RenderData *rd, const Scene return false; } -/* return true if viewname is the first or if the name is NULL or not found */ bool BKE_scene_multiview_is_render_view_first(const RenderData *rd, const char *viewname) { SceneRenderView *srv; @@ -2989,7 +3069,6 @@ bool BKE_scene_multiview_is_render_view_first(const RenderData *rd, const char * return true; } -/* return true if viewname is the last or if the name is NULL or not found */ bool BKE_scene_multiview_is_render_view_last(const RenderData *rd, const char *viewname) { SceneRenderView *srv; @@ -3073,12 +3152,6 @@ void BKE_scene_multiview_filepath_get(SceneRenderView *srv, const char *filepath BLI_path_suffix(r_filepath, FILE_MAX, srv->suffix, ""); } -/** - * 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 - * individual views. - */ void BKE_scene_multiview_view_filepath_get(const RenderData *rd, const char *filepath, const char *viewname, @@ -3466,10 +3539,6 @@ TransformOrientation *BKE_scene_transform_orientation_find(const Scene *scene, c return BLI_findlink(&scene->transform_spaces, index); } -/** - * \return the index that \a orientation has within \a scene's transform-orientation list - * or -1 if not found. - */ int BKE_scene_transform_orientation_get_index(const Scene *scene, const TransformOrientation *orientation) { diff --git a/source/blender/blenkernel/intern/screen.c b/source/blender/blenkernel/intern/screen.c index 0474c2b81cb..6e352b6ba90 100644 --- a/source/blender/blenkernel/intern/screen.c +++ b/source/blender/blenkernel/intern/screen.c @@ -45,6 +45,7 @@ #include "DNA_view3d_types.h" #include "DNA_workspace_types.h" +#include "BLI_ghash.h" #include "BLI_listbase.h" #include "BLI_math_vector.h" #include "BLI_mempool.h" @@ -93,13 +94,13 @@ 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); } } 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. * Will be for a later round of cleanup though... */ @@ -107,24 +108,21 @@ void BKE_screen_foreach_id_screen_area(LibraryForeachIDData *data, ScrArea *area 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 +130,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 +178,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 +203,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,12 +230,13 @@ 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)); } } @@ -267,7 +255,6 @@ static void screen_blend_write(BlendWriter *writer, ID *id, const void *id_addre BKE_screen_area_map_blend_write(writer, AREAMAP_FROM_SCREEN(screen)); } -/* Cannot use IDTypeInfo callback yet, because of the return value. */ bool BKE_screen_blend_read_data(BlendDataReader *reader, bScreen *screen) { bool success = true; @@ -313,6 +300,7 @@ IDTypeInfo IDType_ID_SCR = { .name_plural = "screens", .translation_context = BLT_I18NCONTEXT_ID_SCREEN, .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = NULL, .copy_data = NULL, @@ -320,6 +308,7 @@ IDTypeInfo IDType_ID_SCR = { .make_local = NULL, .foreach_id = screen_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = screen_blend_write, @@ -511,39 +500,35 @@ ARegion *BKE_area_region_copy(const SpaceType *st, const ARegion *region) return newar; } -/* from lb2 to lb1, lb1 is supposed to be freed */ -static void region_copylist(SpaceType *st, ListBase *lb1, ListBase *lb2) +/* from lb_src to lb_dst, lb_dst is supposed to be freed */ +static void region_copylist(SpaceType *st, ListBase *lb_dst, ListBase *lb_src) { /* to be sure */ - BLI_listbase_clear(lb1); + BLI_listbase_clear(lb_dst); - LISTBASE_FOREACH (ARegion *, region, lb2) { + LISTBASE_FOREACH (ARegion *, region, lb_src) { ARegion *region_new = BKE_area_region_copy(st, region); - BLI_addtail(lb1, region_new); + BLI_addtail(lb_dst, region_new); } } -/* lb1 should be empty */ -void BKE_spacedata_copylist(ListBase *lb1, ListBase *lb2) +void BKE_spacedata_copylist(ListBase *lb_dst, ListBase *lb_src) { - BLI_listbase_clear(lb1); /* to be sure */ + BLI_listbase_clear(lb_dst); /* to be sure */ - LISTBASE_FOREACH (SpaceLink *, sl, lb2) { + LISTBASE_FOREACH (SpaceLink *, sl, lb_src) { SpaceType *st = BKE_spacetype_from_id(sl->spacetype); if (st && st->duplicate) { SpaceLink *slnew = st->duplicate(sl); - BLI_addtail(lb1, slnew); + BLI_addtail(lb_dst, slnew); region_copylist(st, &slnew->regionbase, &sl->regionbase); } } } -/* facility to set locks for drawing to survive (render) threads accessing drawing data */ -/* lock can become bitflag too */ -/* should be replaced in future by better local data handling for threads */ void BKE_spacedata_draw_locks(bool set) { LISTBASE_FOREACH (SpaceType *, st, &spacetypes) { @@ -558,10 +543,6 @@ void BKE_spacedata_draw_locks(bool set) } } -/** - * Version of #BKE_area_find_region_type that also works if \a slink - * is not the active space of \a area. - */ ARegion *BKE_spacedata_find_region_type(const SpaceLink *slink, const ScrArea *area, int region_type) @@ -595,7 +576,6 @@ void BKE_spacedata_callback_id_remap_set(void (*func)(ScrArea *area, SpaceLink * spacedata_id_remap_cb = func; } -/* UNUSED!!! */ void BKE_spacedata_id_unref(struct ScrArea *area, struct SpaceLink *sl, struct ID *id) { if (spacedata_id_remap_cb) { @@ -659,7 +639,6 @@ void BKE_area_region_panels_free(ListBase *panels) BLI_listbase_clear(panels); } -/* not region itself */ void BKE_area_region_free(SpaceType *st, ARegion *region) { if (st) { @@ -693,13 +672,17 @@ void BKE_area_region_free(SpaceType *st, ARegion *region) region_free_gizmomap_callback(region->gizmo_map); } + if (region->runtime.block_name_map != NULL) { + BLI_ghash_free(region->runtime.block_name_map, NULL, NULL); + region->runtime.block_name_map = NULL; + } + BLI_freelistN(®ion->ui_lists); BLI_freelistN(®ion->ui_previews); BLI_freelistN(®ion->panels_category); BLI_freelistN(®ion->panels_category_active); } -/* not area itself */ void BKE_screen_area_free(ScrArea *area) { SpaceType *st = BKE_spacetype_from_id(area->spacetype); @@ -727,7 +710,6 @@ void BKE_screen_area_map_free(ScrAreaMap *area_map) BLI_freelistN(&area_map->areabase); } -/** Free (or release) any data used by this screen (does not free the screen itself). */ void BKE_screen_free_data(bScreen *screen) { screen_free_data(&screen->id); @@ -890,12 +872,6 @@ void BKE_screen_remove_unused_scrverts(bScreen *screen) /* ***************** Utilities ********************** */ -/** - * Find a region of type \a region_type in the currently active space of \a area. - * - * \note This does _not_ work if the region to look up is not in the active - * space. Use #BKE_spacedata_find_region_type if that may be the case. - */ ARegion *BKE_area_find_region_type(const ScrArea *area, int region_type) { if (area) { @@ -924,7 +900,7 @@ ARegion *BKE_area_find_region_active_win(ScrArea *area) return BKE_area_find_region_type(area, RGN_TYPE_WINDOW); } -ARegion *BKE_area_find_region_xy(ScrArea *area, const int regiontype, int x, int y) +ARegion *BKE_area_find_region_xy(ScrArea *area, const int regiontype, const int xy[2]) { if (area == NULL) { return NULL; @@ -932,7 +908,7 @@ ARegion *BKE_area_find_region_xy(ScrArea *area, const int regiontype, int x, int LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { if (ELEM(regiontype, RGN_TYPE_ANY, region->regiontype)) { - if (BLI_rcti_isect_pt(®ion->winrct, x, y)) { + if (BLI_rcti_isect_pt_v(®ion->winrct, xy)) { return region; } } @@ -940,14 +916,11 @@ ARegion *BKE_area_find_region_xy(ScrArea *area, const int regiontype, int x, int return NULL; } -/** - * \note This is only for screen level regions (typically menus/popups). - */ -ARegion *BKE_screen_find_region_xy(bScreen *screen, const int regiontype, int x, int y) +ARegion *BKE_screen_find_region_xy(bScreen *screen, const int regiontype, const int xy[2]) { LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { if (ELEM(regiontype, RGN_TYPE_ANY, region->regiontype)) { - if (BLI_rcti_isect_pt(®ion->winrct, x, y)) { + if (BLI_rcti_isect_pt_v(®ion->winrct, xy)) { return region; } } @@ -955,10 +928,6 @@ ARegion *BKE_screen_find_region_xy(bScreen *screen, const int regiontype, int x, return NULL; } -/** - * \note Ideally we can get the area from the context, - * there are a few places however where this isn't practical. - */ ScrArea *BKE_screen_find_area_from_space(struct bScreen *screen, SpaceLink *sl) { LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { @@ -970,10 +939,6 @@ ScrArea *BKE_screen_find_area_from_space(struct bScreen *screen, SpaceLink *sl) return NULL; } -/** - * \note Using this function is generally a last resort, you really want to be - * using the context when you can - campbell - */ ScrArea *BKE_screen_find_big_area(bScreen *screen, const int spacetype, const short min) { ScrArea *big = NULL; @@ -996,11 +961,10 @@ ScrArea *BKE_screen_find_big_area(bScreen *screen, const int spacetype, const sh ScrArea *BKE_screen_area_map_find_area_xy(const ScrAreaMap *areamap, const int spacetype, - int x, - int y) + const int xy[2]) { LISTBASE_FOREACH (ScrArea *, area, &areamap->areabase) { - if (BLI_rcti_isect_pt(&area->totrct, x, y)) { + if (BLI_rcti_isect_pt_v(&area->totrct, xy)) { if (ELEM(spacetype, SPACE_TYPE_ANY, area->spacetype)) { return area; } @@ -1009,9 +973,9 @@ ScrArea *BKE_screen_area_map_find_area_xy(const ScrAreaMap *areamap, } return NULL; } -ScrArea *BKE_screen_find_area_xy(bScreen *screen, const int spacetype, int x, int y) +ScrArea *BKE_screen_find_area_xy(bScreen *screen, const int spacetype, const int xy[2]) { - return BKE_screen_area_map_find_area_xy(AREAMAP_FROM_SCREEN(screen), spacetype, x, y); + return BKE_screen_area_map_find_area_xy(AREAMAP_FROM_SCREEN(screen), spacetype, xy); } void BKE_screen_view3d_sync(View3D *v3d, struct Scene *scene) @@ -1051,26 +1015,20 @@ void BKE_screen_view3d_shading_init(View3DShading *shading) memcpy(shading, shading_default, sizeof(*shading)); } -ARegion *BKE_screen_find_main_region_at_xy(bScreen *screen, - const int space_type, - const int x, - const int y) +ARegion *BKE_screen_find_main_region_at_xy(bScreen *screen, const int space_type, const int xy[2]) { - ScrArea *area = BKE_screen_find_area_xy(screen, space_type, x, y); + ScrArea *area = BKE_screen_find_area_xy(screen, space_type, xy); if (!area) { return NULL; } - return BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, x, y); + return BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, xy); } -/* magic zoom calculation, no idea what - * it signifies, if you find out, tell me! -zr - */ +/* Magic zoom calculation, no idea what it signifies, if you find out, tell me! -zr + * + * Simple, its magic dude! Well, to be honest, + * this gives a natural feeling zooming with multiple keypad presses (ton). */ -/* simple, its magic dude! - * well, to be honest, this gives a natural feeling zooming - * with multiple keypad presses (ton) - */ float BKE_screen_view3d_zoom_to_fac(float camzoom) { return powf(((float)M_SQRT2 + camzoom / 50.0f), 2.0f) / 4.0f; @@ -1485,7 +1443,6 @@ static void direct_link_region(BlendDataReader *reader, ARegion *region, int spa } /* for the saved 2.50 files without regiondata */ -/* and as patch for 2.48 and older */ void BKE_screen_view3d_do_versions_250(View3D *v3d, ListBase *regions) { LISTBASE_FOREACH (ARegion *, region, regions) { @@ -1624,7 +1581,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; @@ -1793,9 +1749,6 @@ static void direct_link_area(BlendDataReader *reader, ScrArea *area) BLO_read_data_address(reader, &area->v4); } -/** - * \return false on error. - */ bool BKE_screen_area_map_blend_read_data(BlendDataReader *reader, ScrAreaMap *area_map) { BLO_read_list(reader, &area_map->vertbase); diff --git a/source/blender/blenkernel/intern/shader_fx.c b/source/blender/blenkernel/intern/shader_fx.c index 12017907038..a0d67a78d0f 100644 --- a/source/blender/blenkernel/intern/shader_fx.c +++ b/source/blender/blenkernel/intern/shader_fx.c @@ -57,7 +57,6 @@ 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(const Object *ob) { const ShaderFxData *fx; @@ -136,7 +135,6 @@ void BKE_shaderfx_free(ShaderFxData *fx) BKE_shaderfx_free_ex(fx, 0); } -/* check unique name */ bool BKE_shaderfx_unique_name(ListBase *shaders, ShaderFxData *fx) { if (shaders && fx) { @@ -164,24 +162,12 @@ const ShaderFxTypeInfo *BKE_shaderfx_get_info(ShaderFxType type) return NULL; } -/** - * Check whether given shaderfx is not local (i.e. from linked data) when the object is a library - * override. - * - * \param shaderfx: May be NULL, in which case we consider it as a non-local shaderfx case. - */ bool BKE_shaderfx_is_nonlocal_in_liboverride(const Object *ob, const ShaderFxData *shaderfx) { return (ID_IS_OVERRIDE_LIBRARY(ob) && ((shaderfx == NULL) || (shaderfx->flag & eShaderFxFlag_OverrideLibrary_Local) == 0)); } -/** - * Get an effect's panel type, which was defined in the #panelRegister callback. - * - * \note ShaderFx panel types are assumed to be named with the struct name field concatenated to - * the defined prefix. - */ void BKE_shaderfxType_panel_id(ShaderFxType type, char *r_idname) { const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(type); diff --git a/source/blender/blenkernel/intern/shrinkwrap.c b/source/blender/blenkernel/intern/shrinkwrap.c index 7c0c28d664e..d51ed2832f0 100644 --- a/source/blender/blenkernel/intern/shrinkwrap.c +++ b/source/blender/blenkernel/intern/shrinkwrap.c @@ -28,6 +28,7 @@ #include <string.h> #include <time.h> +#include "DNA_gpencil_modifier_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" @@ -74,7 +75,8 @@ typedef struct ShrinkwrapCalcData { struct Object *ob; /* object we are applying shrinkwrap to */ - struct MVert *vert; /* Array of verts being projected (to fetch normals or other data) */ + struct MVert *vert; /* Array of verts being projected. */ + const float (*vert_normals)[3]; float (*vertexCos)[3]; /* vertexs being shrinkwraped */ int numVerts; @@ -101,7 +103,6 @@ typedef struct ShrinkwrapCalcCBData { SpaceTransform *local2aux; } ShrinkwrapCalcCBData; -/* Checks if the modifier needs target normals with these settings. */ bool BKE_shrinkwrap_needs_normals(int shrinkType, int shrinkMode) { return (shrinkType == MOD_SHRINKWRAP_TARGET_PROJECT) || @@ -109,7 +110,6 @@ bool BKE_shrinkwrap_needs_normals(int shrinkType, int shrinkMode) shrinkMode == MOD_SHRINKWRAP_ABOVE_SURFACE); } -/* Initializes the mesh data structure from the given mesh and settings. */ bool BKE_shrinkwrap_init_tree( ShrinkwrapTreeData *data, Mesh *mesh, int shrinkType, int shrinkMode, bool force_normals) { @@ -147,7 +147,7 @@ bool BKE_shrinkwrap_init_tree( } if (force_normals || BKE_shrinkwrap_needs_normals(shrinkType, shrinkMode)) { - data->pnors = CustomData_get_layer(&mesh->pdata, CD_NORMAL); + data->pnors = BKE_mesh_poly_normals_ensure(mesh); if ((mesh->flag & ME_AUTOSMOOTH) != 0) { data->clnors = CustomData_get_layer(&mesh->ldata, CD_NORMAL); } @@ -160,13 +160,11 @@ bool BKE_shrinkwrap_init_tree( return true; } -/* Frees the tree data if necessary. */ void BKE_shrinkwrap_free_tree(ShrinkwrapTreeData *data) { free_bvhtree_from_mesh(&data->treeData); } -/* Free boundary data for target project */ void BKE_shrinkwrap_discard_boundary_data(struct Mesh *mesh) { struct ShrinkwrapBoundaryData *data = mesh->runtime.shrinkwrap_data; @@ -316,18 +314,18 @@ static ShrinkwrapBoundaryData *shrinkwrap_build_boundary_data(struct Mesh *mesh) MEM_freeN(vert_status); /* Finalize average direction and compute normal. */ + const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh); for (int i = 0; i < mesh->totvert; i++) { int bidx = vert_boundary_id[i]; if (bidx >= 0) { ShrinkwrapBoundaryVertData *vdata = &boundary_verts[bidx]; - float no[3], tmp[3]; + float tmp[3]; normalize_v3(vdata->direction); - normal_short_to_float_v3(no, mesh->mvert[i].no); - cross_v3_v3v3(tmp, no, vdata->direction); - cross_v3_v3v3(vdata->normal_plane, tmp, no); + cross_v3_v3v3(tmp, vert_normals[i], vdata->direction); + cross_v3_v3v3(vdata->normal_plane, tmp, vert_normals[i]); normalize_v3(vdata->normal_plane); } } @@ -434,14 +432,6 @@ static void shrinkwrap_calc_nearest_vertex(ShrinkwrapCalcData *calc) 0, calc->numVerts, &data, shrinkwrap_calc_nearest_vertex_cb_ex, &settings); } -/* - * This function raycast a single vertex and updates the hit if the "hit" is considered valid. - * Returns true if "hit" was updated. - * Opts control whether an hit is valid or not - * Supported options are: - * - MOD_SHRINKWRAP_CULL_TARGET_FRONTFACE (front faces hits are ignored) - * - MOD_SHRINKWRAP_CULL_TARGET_BACKFACE (back faces hits are ignored) - */ bool BKE_shrinkwrap_project_normal(char options, const float vert[3], const float dir[3], @@ -551,7 +541,7 @@ static void shrinkwrap_calc_normal_projection_cb_ex(void *__restrict userdata, * (to get correct normals) for other cases calc->verts contains undeformed coordinates and * vertexCos should be used */ copy_v3_v3(tmp_co, calc->vert[i].co); - normal_short_to_float_v3(tmp_no, calc->vert[i].no); + copy_v3_v3(tmp_no, calc->vert_normals[i]); } else { copy_v3_v3(tmp_co, co); @@ -644,7 +634,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, @@ -1019,8 +1009,8 @@ static void target_project_edge(const ShrinkwrapTreeData *tree, CLAMP(x, 0, 1); float vedge_no[2][3]; - normal_short_to_float_v3(vedge_no[0], data->vert[edge->v1].no); - normal_short_to_float_v3(vedge_no[1], data->vert[edge->v2].no); + copy_v3_v3(vedge_no[0], data->vert_normals[edge->v1]); + copy_v3_v3(vedge_no[1], data->vert_normals[edge->v2]); interp_v3_v3v3(hit_co, vedge_co[0], vedge_co[1], x); interp_v3_v3v3(hit_no, vedge_no[0], vedge_no[1], x); @@ -1066,9 +1056,9 @@ static void mesh_looptri_target_project(void *userdata, } /* Decode normals */ - normal_short_to_float_v3(vtri_no[0], vtri[0]->no); - normal_short_to_float_v3(vtri_no[1], vtri[1]->no); - normal_short_to_float_v3(vtri_no[2], vtri[2]->no); + copy_v3_v3(vtri_no[0], tree->treeData.vert_normals[loop[0]->v]); + copy_v3_v3(vtri_no[1], tree->treeData.vert_normals[loop[1]->v]); + copy_v3_v3(vtri_no[2], tree->treeData.vert_normals[loop[2]->v]); /* Solve the equations for the triangle */ if (target_project_solve_point_tri(vtri_co, vtri_no, co, raw_hit_co, dist_sq, hit_co, hit_no)) { @@ -1089,9 +1079,6 @@ static void mesh_looptri_target_project(void *userdata, } } -/* - * Maps the point to the nearest surface, either by simple nearest, or by target normal projection. - */ void BKE_shrinkwrap_find_nearest_surface(struct ShrinkwrapTreeData *tree, BVHTreeNearest *nearest, float co[3], @@ -1196,13 +1183,6 @@ static void shrinkwrap_calc_nearest_surface_point_cb_ex(void *__restrict userdat } } -/** - * Compute a smooth normal of the target (if applicable) at the hit location. - * - * \param tree: information about the mesh - * \param transform: transform from the hit coordinate space to the object space; may be null - * \param r_no: output in hit coordinate space; may be shared with inputs - */ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, const struct SpaceTransform *transform, int looptri_idx, @@ -1212,14 +1192,13 @@ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, { const BVHTreeFromMesh *treeData = &tree->treeData; const MLoopTri *tri = &treeData->looptri[looptri_idx]; + const float(*vert_normals)[3] = tree->treeData.vert_normals; /* Interpolate smooth normals if enabled. */ if ((tree->mesh->mpoly[tri->poly].flag & ME_SMOOTH) != 0) { - const MVert *verts[] = { - &treeData->vert[treeData->loop[tri->tri[0]].v], - &treeData->vert[treeData->loop[tri->tri[1]].v], - &treeData->vert[treeData->loop[tri->tri[2]].v], - }; + const uint32_t vert_indices[3] = {treeData->loop[tri->tri[0]].v, + treeData->loop[tri->tri[1]].v, + treeData->loop[tri->tri[2]].v}; float w[3], no[3][3], tmp_co[3]; /* Custom and auto smooth split normals. */ @@ -1230,9 +1209,9 @@ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, } /* Ordinary vertex normals. */ else { - normal_short_to_float_v3(no[0], verts[0]->no); - normal_short_to_float_v3(no[1], verts[1]->no); - normal_short_to_float_v3(no[2], verts[2]->no); + copy_v3_v3(no[0], vert_normals[vert_indices[0]]); + copy_v3_v3(no[1], vert_normals[vert_indices[1]]); + copy_v3_v3(no[2], vert_normals[vert_indices[2]]); } /* Barycentric weights from hit point. */ @@ -1242,7 +1221,11 @@ void BKE_shrinkwrap_compute_smooth_normal(const struct ShrinkwrapTreeData *tree, BLI_space_transform_apply(transform, tmp_co); } - interp_weights_tri_v3(w, verts[0]->co, verts[1]->co, verts[2]->co, tmp_co); + interp_weights_tri_v3(w, + treeData->vert[vert_indices[0]].co, + treeData->vert[vert_indices[1]].co, + treeData->vert[vert_indices[2]].co, + tmp_co); /* Interpolate using weights. */ interp_v3_v3v3v3(r_no, no[0], no[1], no[2], w); @@ -1318,13 +1301,6 @@ static void shrinkwrap_snap_with_side(float r_point_co[3], } } -/** - * Apply the shrink to surface modes to the given original coordinates and nearest point. - * - * \param tree: mesh data for smooth normals - * \param transform: transform from the hit coordinate space to the object space; may be null - * \param r_point_co: may be the same memory location as point_co, hit_co, or hit_no. - */ void BKE_shrinkwrap_snap_point_to_surface(const struct ShrinkwrapTreeData *tree, const struct SpaceTransform *transform, int mode, @@ -1404,7 +1380,6 @@ static void shrinkwrap_calc_nearest_surface_point(ShrinkwrapCalcData *calc) 0, calc->numVerts, &data, shrinkwrap_calc_nearest_surface_point_cb_ex, &settings); } -/* Main shrinkwrap function */ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd, const ModifierEvalContext *ctx, struct Scene *scene, @@ -1453,6 +1428,7 @@ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd, if (mesh != NULL && smd->shrinkType == MOD_SHRINKWRAP_PROJECT) { /* Setup arrays to get vertexs positions, normals and deform weights */ calc.vert = mesh->mvert; + calc.vert_normals = BKE_mesh_vertex_normals_ensure(mesh); /* Using vertexs positions/normals as if a subsurface was applied */ if (smd->subsurfLevels) { @@ -1513,6 +1489,55 @@ void shrinkwrapModifier_deform(ShrinkwrapModifierData *smd, } } +void shrinkwrapGpencilModifier_deform(ShrinkwrapGpencilModifierData *mmd, + Object *ob, + MDeformVert *dvert, + const int defgrp_index, + float (*vertexCos)[3], + int numVerts) +{ + + ShrinkwrapCalcData calc = NULL_ShrinkwrapCalcData; + /* Convert gpencil struct to use the same struct and function used with meshes. */ + ShrinkwrapModifierData smd; + smd.target = mmd->target; + smd.auxTarget = mmd->aux_target; + smd.keepDist = mmd->keep_dist; + smd.shrinkType = mmd->shrink_type; + smd.shrinkOpts = mmd->shrink_opts; + smd.shrinkMode = mmd->shrink_mode; + smd.projLimit = mmd->proj_limit; + smd.projAxis = mmd->proj_axis; + + /* Configure Shrinkwrap calc data. */ + calc.smd = &smd; + calc.ob = ob; + calc.numVerts = numVerts; + calc.vertexCos = vertexCos; + calc.dvert = dvert; + calc.vgroup = defgrp_index; + calc.invert_vgroup = (mmd->flag & GP_SHRINKWRAP_INVERT_VGROUP) != 0; + + BLI_SPACE_TRANSFORM_SETUP(&calc.local2target, ob, mmd->target); + calc.keepDist = mmd->keep_dist; + calc.tree = mmd->cache_data; + + switch (mmd->shrink_type) { + case MOD_SHRINKWRAP_NEAREST_SURFACE: + case MOD_SHRINKWRAP_TARGET_PROJECT: + TIMEIT_BENCH(shrinkwrap_calc_nearest_surface_point(&calc), gpdeform_surface); + break; + + case MOD_SHRINKWRAP_PROJECT: + TIMEIT_BENCH(shrinkwrap_calc_normal_projection(&calc), gpdeform_project); + break; + + case MOD_SHRINKWRAP_NEAREST_VERTEX: + TIMEIT_BENCH(shrinkwrap_calc_nearest_vertex(&calc), gpdeform_vertex); + break; + } +} + void BKE_shrinkwrap_mesh_nearest_surface_deform(struct bContext *C, Object *ob_source, Object *ob_target) @@ -1561,6 +1586,7 @@ void BKE_shrinkwrap_remesh_target_project(Mesh *src_me, Mesh *target_me, Object calc.smd = &ssmd; calc.numVerts = src_me->totvert; calc.vertexCos = vertexCos; + calc.vert_normals = BKE_mesh_vertex_normals_ensure(src_me); calc.vgroup = -1; calc.target = target_me; calc.keepDist = ssmd.keepDist; diff --git a/source/blender/blenkernel/intern/simulation.cc b/source/blender/blenkernel/intern/simulation.cc index 1d297b3ced9..ec4b0e8d51d 100644 --- a/source/blender/blenkernel/intern/simulation.cc +++ b/source/blender/blenkernel/intern/simulation.cc @@ -28,9 +28,9 @@ #include "DNA_simulation_types.h" #include "BLI_compiler_compat.h" -#include "BLI_float3.hh" #include "BLI_listbase.h" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_rand.h" #include "BLI_span.hh" #include "BLI_string.h" @@ -103,7 +103,8 @@ 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)); } } @@ -153,6 +154,7 @@ IDTypeInfo IDType_ID_SIM = { /* name_plural */ "simulations", /* translation_context */ BLT_I18NCONTEXT_ID_SIMULATION, /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, /* init_data */ simulation_init_data, /* copy_data */ simulation_copy_data, @@ -160,6 +162,7 @@ IDTypeInfo IDType_ID_SIM = { /* make_local */ nullptr, /* foreach_id */ simulation_foreach_id, /* foreach_cache */ nullptr, + /* foreach_path */ nullptr, /* owner_get */ nullptr, /* blend_write */ simulation_blend_write, diff --git a/source/blender/blenkernel/intern/softbody.c b/source/blender/blenkernel/intern/softbody.c index fbc781f5eb9..baabf57f0c3 100644 --- a/source/blender/blenkernel/intern/softbody.c +++ b/source/blender/blenkernel/intern/softbody.c @@ -1673,7 +1673,7 @@ static int sb_detect_vertex_collisionCached(float opco[3], if ((opco[0] < minx) || (opco[1] < miny) || (opco[2] < minz) || (opco[0] > maxx) || (opco[1] > maxy) || (opco[2] > maxz)) { - /* outside the padded boundbox --> collision object is too far away */ + /* Outside the padded bound-box -> collision object is too far away. */ BLI_ghashIterator_step(ihash); continue; } @@ -2295,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) { @@ -2633,7 +2633,7 @@ static void interpolate_exciter(Object *ob, int timescale, int time) } } -/* ************ convertors ********** */ +/* ************ converters ********** */ /* for each object type we need; * - xxxx_to_softbody(Object *ob) : a full (new) copy, creates SB geometry @@ -3112,7 +3112,6 @@ static void sb_new_scratch(SoftBody *sb) /* ************ Object level, exported functions *************** */ -/* allocates and initializes general main data */ SoftBody *sbNew(void) { SoftBody *sb; @@ -3162,7 +3161,6 @@ SoftBody *sbNew(void) return sb; } -/* frees all */ void sbFree(Object *ob) { SoftBody *sb = ob->soft; @@ -3193,7 +3191,6 @@ void sbFreeSimulation(SoftBody *sb) free_softbody_intern(sb); } -/* makes totally fresh start situation */ void sbObjectToSoftbody(Object *ob) { // ob->softflag |= OB_SB_REDO; @@ -3213,7 +3210,6 @@ static bool object_has_edges(const Object *ob) return false; } -/* SB global visible functions */ void sbSetInterruptCallBack(int (*f)(void)) { SB_localInterruptCallBack = f; @@ -3244,20 +3240,6 @@ static void softbody_update_positions(Object *ob, } } -/* void SB_estimate_transform */ -/* input Object *ob out (says any object that can do SB like mesh, lattice, curve ) - * output float lloc[3], float lrot[3][3], float lscale[3][3] - * that is: - * a precise position vector denoting the motion of the center of mass - * give a rotation/scale matrix using averaging method, that's why estimate and not calculate - * see: this is kind of reverse engineering: having to states of a point cloud and recover what - * happened our advantage here we know the identity of the vertex there are others methods giving - * other results. lloc, lrot, lscale are allowed to be NULL, just in case you don't need it. - * should be pretty useful for pythoneers :) - * not! velocity .. 2nd order stuff - * vcloud_estimate_transform_v3 see - */ - void SB_estimate_transform(Object *ob, float lloc[3], float lrot[3][3], float lscale[3][3]) { BodyPoint *bp; @@ -3523,7 +3505,6 @@ static void sbStoreLastFrame(struct Depsgraph *depsgraph, Object *object, float object_orig->soft->last_frame = framenr; } -/* simulates one step. framenr is in frames */ void sbObjectStep(struct Depsgraph *depsgraph, Scene *scene, Object *ob, diff --git a/source/blender/blenkernel/intern/sound.c b/source/blender/blenkernel/intern/sound.c index 8feda76cc5b..b27231e6a17 100644 --- a/source/blender/blenkernel/intern/sound.c +++ b/source/blender/blenkernel/intern/sound.c @@ -54,6 +54,7 @@ # include <AUD_Special.h> #endif +#include "BKE_bpath.h" #include "BKE_global.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" @@ -133,6 +134,17 @@ static void sound_foreach_cache(ID *id, function_callback(id, &key, &sound->waveform, 0, user_data); } +static void sound_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + bSound *sound = (bSound *)id; + if (sound->packedfile != NULL && (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0) { + return; + } + + /* FIXME: This does not check for empty path... */ + BKE_bpath_foreach_path_fixed_process(bpath_data, sound->filepath); +} + static void sound_blend_write(BlendWriter *writer, ID *id, const void *id_address) { bSound *sound = (bSound *)id; @@ -205,6 +217,7 @@ IDTypeInfo IDType_ID_SO = { .name_plural = "sounds", .translation_context = BLT_I18NCONTEXT_ID_SOUND, .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, /* A fuzzy case, think NULLified content is OK here... */ .init_data = NULL, @@ -213,6 +226,7 @@ IDTypeInfo IDType_ID_SO = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = sound_foreach_cache, + .foreach_path = sound_foreach_path, .owner_get = NULL, .blend_write = sound_blend_write, @@ -257,14 +271,11 @@ BLI_INLINE void sound_verify_evaluated_id(const ID *id) bSound *BKE_sound_new_file(Main *bmain, const char *filepath) { bSound *sound; - const char *path; + const char *blendfile_path = BKE_main_blendfile_path(bmain); char str[FILE_MAX]; BLI_strncpy(str, filepath, sizeof(str)); - - path = BKE_main_blendfile_path(bmain); - - BLI_path_abs(str, path); + BLI_path_abs(str, blendfile_path); sound = BKE_libblock_alloc(bmain, ID_SO, BLI_path_basename(filepath), 0); BLI_strncpy(sound->filepath, filepath, FILE_MAX); @@ -702,13 +713,13 @@ void *BKE_sound_scene_add_scene_sound( Scene *scene, Sequence *sequence, int startframe, int endframe, int frameskip) { sound_verify_evaluated_id(&scene->id); - if (sequence->scene && scene != sequence->scene && sequence->sound) { + if (sequence->scene && scene != sequence->scene) { const double fps = FPS; return AUD_Sequence_add(scene->sound_scene, sequence->scene->sound_scene, startframe / fps, endframe / fps, - frameskip / fps + sequence->sound->offset_time); + frameskip / fps); } return NULL; } @@ -774,13 +785,13 @@ void BKE_sound_move_scene_sound( void BKE_sound_move_scene_sound_defaults(Scene *scene, Sequence *sequence) { sound_verify_evaluated_id(&scene->id); - if (sequence->scene_sound && sequence->sound) { + if (sequence->scene_sound) { BKE_sound_move_scene_sound(scene, sequence->scene_sound, sequence->startdisp, sequence->enddisp, sequence->startofs + sequence->anim_startofs, - sequence->sound->offset_time); + 0.0); } } @@ -1235,15 +1246,14 @@ bool BKE_sound_stream_info_get(struct Main *main, int stream, SoundStreamInfo *sound_info) { - const char *path; + const char *blendfile_path = BKE_main_blendfile_path(main); 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); + BLI_path_abs(str, blendfile_path); sound = AUD_Sound_file(str); if (!sound) { diff --git a/source/blender/blenkernel/intern/speaker.c b/source/blender/blenkernel/intern/speaker.c index b361f31cc30..b7199dc1e20 100644 --- a/source/blender/blenkernel/intern/speaker.c +++ b/source/blender/blenkernel/intern/speaker.c @@ -50,7 +50,7 @@ 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) @@ -99,6 +99,7 @@ IDTypeInfo IDType_ID_SPK = { .name_plural = "speakers", .translation_context = BLT_I18NCONTEXT_ID_SPEAKER, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = speaker_init_data, .copy_data = NULL, @@ -106,6 +107,7 @@ IDTypeInfo IDType_ID_SPK = { .make_local = NULL, .foreach_id = speaker_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = speaker_blend_write, diff --git a/source/blender/blenkernel/intern/spline_base.cc b/source/blender/blenkernel/intern/spline_base.cc index 663c1951ba3..3262d768b6c 100644 --- a/source/blender/blenkernel/intern/spline_base.cc +++ b/source/blender/blenkernel/intern/spline_base.cc @@ -30,14 +30,12 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::VArray; using blender::attribute_math::convert_to_static_type; using blender::bke::AttributeIDRef; using blender::fn::GMutableSpan; using blender::fn::GSpan; using blender::fn::GVArray; -using blender::fn::GVArray_For_GSpan; -using blender::fn::GVArray_Typed; -using blender::fn::GVArrayPtr; Spline::Type Spline::type() const { @@ -64,9 +62,6 @@ static SplinePtr create_spline(const Spline::Type type) return {}; } -/** - * Return a new spline with the same data, settings, and attributes. - */ SplinePtr Spline::copy() const { SplinePtr dst = this->copy_without_attributes(); @@ -74,9 +69,6 @@ SplinePtr Spline::copy() const return dst; } -/** - * Return a new spline with the same type and settings like "cyclic", but without any data. - */ SplinePtr Spline::copy_only_settings() const { SplinePtr dst = create_spline(type_); @@ -85,9 +77,6 @@ SplinePtr Spline::copy_only_settings() const return dst; } -/** - * The same as #copy, but skips copying dynamic attributes to the new spline. - */ SplinePtr Spline::copy_without_attributes() const { SplinePtr dst = this->copy_only_settings(); @@ -177,22 +166,18 @@ static void accumulate_lengths(Span<float3> positions, const bool is_cyclic, MutableSpan<float> lengths) { + using namespace blender::math; + float length = 0.0f; for (const int i : IndexRange(positions.size() - 1)) { - length += float3::distance(positions[i], positions[i + 1]); + length += distance(positions[i], positions[i + 1]); lengths[i] = length; } if (is_cyclic) { - lengths.last() = length + float3::distance(positions.last(), positions.first()); + lengths.last() = length + distance(positions.last(), positions.first()); } } -/** - * 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, and it is necessarily single threaded, so it is cached. - */ Span<float> Spline::evaluated_lengths() const { if (!length_cache_dirty_) { @@ -217,11 +202,13 @@ Span<float> Spline::evaluated_lengths() const static float3 direction_bisect(const float3 &prev, const float3 &middle, const float3 &next) { - const float3 dir_prev = (middle - prev).normalized(); - const float3 dir_next = (next - middle).normalized(); + using namespace blender::math; + + const float3 dir_prev = normalize(middle - prev); + const float3 dir_next = normalize(next - middle); - const float3 result = (dir_prev + dir_next).normalized(); - if (UNLIKELY(result.is_zero())) { + const float3 result = normalize(dir_prev + dir_next); + if (UNLIKELY(is_zero(result))) { return float3(0.0f, 0.0f, 1.0f); } return result; @@ -231,6 +218,8 @@ static void calculate_tangents(Span<float3> positions, const bool is_cyclic, MutableSpan<float3> tangents) { + using namespace blender::math; + if (positions.size() == 1) { tangents.first() = float3(0.0f, 0.0f, 1.0f); return; @@ -249,14 +238,11 @@ static void calculate_tangents(Span<float3> positions, tangents.last() = direction_bisect(second_to_last, last, first); } else { - tangents.first() = (positions[1] - positions[0]).normalized(); - tangents.last() = (positions.last() - positions[positions.size() - 2]).normalized(); + tangents.first() = normalize(positions[1] - positions[0]); + tangents.last() = normalize(positions.last() - positions[positions.size() - 2]); } } -/** - * Return non-owning access to the direction of the curve at each evaluated point. - */ Span<float3> Spline::evaluated_tangents() const { if (!tangent_cache_dirty_) { @@ -284,18 +270,22 @@ static float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle) { + using namespace blender::math; + BLI_ASSERT_UNIT_V3(direction); BLI_ASSERT_UNIT_V3(axis); - const float3 axis_scaled = axis * float3::dot(direction, axis); + const float3 axis_scaled = axis * dot(direction, axis); const float3 diff = direction - axis_scaled; - const float3 cross = float3::cross(axis, diff); + const float3 cross = blender::math::cross(axis, diff); return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle); } static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> r_normals) { + using namespace blender::math; + BLI_assert(r_normals.size() == tangents.size()); /* Same as in `vec_to_quat`. */ @@ -306,7 +296,7 @@ static void calculate_normals_z_up(Span<float3> tangents, MutableSpan<float3> r_ r_normals[i] = {1.0f, 0.0f, 0.0f}; } else { - r_normals[i] = float3(tangent.y, -tangent.x, 0.0f).normalized(); + r_normals[i] = normalize(float3(tangent.y, -tangent.x, 0.0f)); } } } @@ -318,12 +308,14 @@ static float3 calculate_next_normal(const float3 &last_normal, const float3 &last_tangent, const float3 ¤t_tangent) { - if (last_tangent.is_zero() || current_tangent.is_zero()) { + using namespace blender::math; + + if (is_zero(last_tangent) || is_zero(current_tangent)) { return last_normal; } const float angle = angle_normalized_v3v3(last_tangent, current_tangent); if (angle != 0.0) { - const float3 axis = float3::cross(last_tangent, current_tangent).normalized(); + const float3 axis = normalize(cross(last_tangent, current_tangent)); return rotate_direction_around_axis(last_normal, axis, angle); } return last_normal; @@ -333,6 +325,7 @@ static void calculate_normals_minimum(Span<float3> tangents, const bool cyclic, MutableSpan<float3> r_normals) { + using namespace blender::math; BLI_assert(r_normals.size() == tangents.size()); if (r_normals.is_empty()) { @@ -347,7 +340,7 @@ static void calculate_normals_minimum(Span<float3> tangents, r_normals[0] = {1.0f, 0.0f, 0.0f}; } else { - r_normals[0] = float3(first_tangent.y, -first_tangent.x, 0.0f).normalized(); + r_normals[0] = normalize(float3(first_tangent.y, -first_tangent.x, 0.0f)); } /* Forward normal with minimum twist along the entire spline. */ @@ -377,10 +370,6 @@ static void calculate_normals_minimum(Span<float3> tangents, } } -/** - * Return non-owning access to the direction vectors perpendicular to the tangents at every - * evaluated point. The method used to generate the normal vectors depends on Spline.normal_mode. - */ Span<float3> Spline::evaluated_normals() const { if (!normal_cache_dirty_) { @@ -416,7 +405,7 @@ Span<float3> Spline::evaluated_normals() const } /* Rotate the generated normals with the interpolated tilt data. */ - GVArray_Typed<float> tilts = this->interpolate_to_evaluated(this->tilts()); + VArray<float> tilts = this->interpolate_to_evaluated(this->tilts()); for (const int i : normals.index_range()) { normals[i] = rotate_direction_around_axis(normals[i], tangents[i], tilts[i]); } @@ -430,9 +419,6 @@ Spline::LookupResult Spline::lookup_evaluated_factor(const float factor) const return this->lookup_evaluated_length(this->length() * factor); } -/** - * \note This does not support extrapolation currently. - */ Spline::LookupResult Spline::lookup_evaluated_length(const float length) const { BLI_assert(length >= 0.0f && length <= this->length()); @@ -444,16 +430,13 @@ Spline::LookupResult Spline::lookup_evaluated_length(const float length) const 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); + const float length_in_segment = length - previous_length; + const float segment_length = lengths[index] - previous_length; + const float factor = segment_length == 0.0f ? 0.0f : length_in_segment / segment_length; return LookupResult{index, next_index, factor}; } -/** - * Return an array of evenly spaced samples along the length of the spline. The samples are indices - * and factors to the next index encoded in floats. The logic for converting from the float values - * to interpolation data is in #lookup_data_from_index_factor. - */ Array<float> Spline::sample_uniform_index_factors(const int samples_size) const { const Span<float> lengths = this->evaluated_lengths(); @@ -486,6 +469,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(); @@ -523,16 +512,11 @@ void Spline::bounds_min_max(float3 &min, float3 &max, const bool use_evaluated) } } -GVArrayPtr Spline::interpolate_to_evaluated(GSpan data) const +GVArray Spline::interpolate_to_evaluated(GSpan data) const { - return this->interpolate_to_evaluated(GVArray_For_GSpan(data)); + return this->interpolate_to_evaluated(GVArray::ForSpan(data)); } -/** - * Sample any input data with a value for each evaluated point (already interpolated to evaluated - * points) to arbitrary parameters in between the evaluated points. The interpolation is quite - * simple, but this handles the cyclic and end point special cases. - */ void Spline::sample_with_index_factors(const GVArray &src, Span<float> index_factors, GMutableSpan dst) const @@ -541,7 +525,7 @@ void Spline::sample_with_index_factors(const GVArray &src, blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); - const GVArray_Typed<T> src_typed = src.typed<T>(); + const VArray<T> src_typed = src.typed<T>(); MutableSpan<T> dst_typed = dst.typed<T>(); if (src.size() == 1) { dst_typed.fill(src_typed[0]); diff --git a/source/blender/blenkernel/intern/spline_bezier.cc b/source/blender/blenkernel/intern/spline_bezier.cc index b36d7a21669..980437014b1 100644 --- a/source/blender/blenkernel/intern/spline_bezier.cc +++ b/source/blender/blenkernel/intern/spline_bezier.cc @@ -25,9 +25,8 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::VArray; using blender::fn::GVArray; -using blender::fn::GVArray_For_ArrayContainer; -using blender::fn::GVArrayPtr; void BezierSpline::copy_settings(Spline &dst) const { @@ -71,27 +70,6 @@ void BezierSpline::set_resolution(const int value) this->mark_cache_invalid(); } -/** - * \warning Call #reallocate on the spline's attributes after adding all points. - */ -void BezierSpline::add_point(const float3 position, - const HandleType handle_type_left, - const float3 handle_position_left, - const HandleType handle_type_right, - const float3 handle_position_right, - const float radius, - const float tilt) -{ - handle_types_left_.append(handle_type_left); - handle_positions_left_.append(handle_position_left); - positions_.append(position); - handle_types_right_.append(handle_type_right); - handle_positions_right_.append(handle_position_right); - radii_.append(radius); - tilts_.append(tilt); - this->mark_cache_invalid(); -} - void BezierSpline::resize(const int size) { handle_types_left_.resize(size); @@ -142,11 +120,14 @@ Span<float3> BezierSpline::handle_positions_left() const this->ensure_auto_handles(); return handle_positions_left_; } -MutableSpan<float3> BezierSpline::handle_positions_left() +MutableSpan<float3> BezierSpline::handle_positions_left(const bool write_only) { - this->ensure_auto_handles(); + if (!write_only) { + this->ensure_auto_handles(); + } return handle_positions_left_; } + Span<BezierSpline::HandleType> BezierSpline::handle_types_right() const { return handle_types_right_; @@ -160,9 +141,11 @@ Span<float3> BezierSpline::handle_positions_right() const this->ensure_auto_handles(); return handle_positions_right_; } -MutableSpan<float3> BezierSpline::handle_positions_right() +MutableSpan<float3> BezierSpline::handle_positions_right(const bool write_only) { - this->ensure_auto_handles(); + if (!write_only) { + this->ensure_auto_handles(); + } return handle_positions_right_; } @@ -199,10 +182,6 @@ static float3 next_position(Span<float3> positions, const bool cyclic, const int return positions[i + 1]; } -/** - * Recalculate all #Auto and #Vector handles with positions automatically - * derived from the neighboring control points. - */ void BezierSpline::ensure_auto_handles() const { if (!auto_handles_dirty_) { @@ -220,11 +199,13 @@ void BezierSpline::ensure_auto_handles() const } for (const int i : IndexRange(this->size())) { + using namespace blender; + if (ELEM(HandleType::Auto, handle_types_left_[i], handle_types_right_[i])) { const float3 prev_diff = positions_[i] - previous_position(positions_, is_cyclic_, i); const float3 next_diff = next_position(positions_, is_cyclic_, i) - positions_[i]; - float prev_len = prev_diff.length(); - float next_len = next_diff.length(); + float prev_len = math::length(prev_diff); + float next_len = math::length(next_diff); if (prev_len == 0.0f) { prev_len = 1.0f; } @@ -234,7 +215,7 @@ void BezierSpline::ensure_auto_handles() const const float3 dir = next_diff / next_len + prev_diff / prev_len; /* This magic number is unfortunate, but comes from elsewhere in Blender. */ - const float len = dir.length() * 2.5614f; + const float len = math::length(dir) * 2.5614f; if (len != 0.0f) { if (handle_types_left_[i] == HandleType::Auto) { const float prev_len_clamped = std::min(prev_len, next_len * 5.0f); @@ -249,12 +230,12 @@ void BezierSpline::ensure_auto_handles() const if (handle_types_left_[i] == HandleType::Vector) { const float3 prev = previous_position(positions_, is_cyclic_, i); - handle_positions_left_[i] = float3::interpolate(positions_[i], prev, 1.0f / 3.0f); + handle_positions_left_[i] = math::interpolate(positions_[i], prev, 1.0f / 3.0f); } if (handle_types_right_[i] == HandleType::Vector) { const float3 next = next_position(positions_, is_cyclic_, i); - handle_positions_right_[i] = float3::interpolate(positions_[i], next, 1.0f / 3.0f); + handle_positions_right_[i] = math::interpolate(positions_[i], next, 1.0f / 3.0f); } } @@ -289,6 +270,50 @@ 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) +{ + using namespace blender::math; + + /* 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 = distance(handle_other, position); + /* Set the other handle to directly opposite from the current handle. */ + const float3 dir = normalize(handle - position); + handle_other = position - dir * length; + } +} + +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]); +} + +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) || @@ -297,6 +322,9 @@ bool BezierSpline::point_is_sharp(const int index) const 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 && @@ -327,14 +355,9 @@ int BezierSpline::evaluated_points_size() const return this->control_point_offsets().last(); } -/** - * If the spline is not cyclic, the direction for the first and last points is just the - * direction formed by the corresponding handles and control points. In the unlikely situation - * that the handles define a zero direction, fallback to using the direction defined by the - * first and last evaluated segments already calculated in #Spline::evaluated_tangents(). - */ void BezierSpline::correct_end_tangents() const { + using namespace blender::math; if (is_cyclic_) { return; } @@ -342,50 +365,33 @@ void BezierSpline::correct_end_tangents() const MutableSpan<float3> tangents(evaluated_tangents_cache_); if (handle_positions_right_.first() != positions_.first()) { - tangents.first() = (handle_positions_right_.first() - positions_.first()).normalized(); + tangents.first() = normalize(handle_positions_right_.first() - positions_.first()); } if (handle_positions_left_.last() != positions_.last()) { - tangents.last() = (positions_.last() - handle_positions_left_.last()).normalized(); + tangents.last() = normalize(positions_.last() - handle_positions_left_.last()); } } -/** - * 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) { + using namespace blender::math; + 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); + const float3 center_point = 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); + result.handle_prev = interpolate(point_prev, handle_prev, parameter); + result.handle_next = interpolate(handle_next, point_next, parameter); + result.left_handle = interpolate(result.handle_prev, center_point, parameter); + result.right_handle = interpolate(center_point, result.handle_next, parameter); + result.position = interpolate(result.left_handle, result.right_handle, parameter); return result; } @@ -433,15 +439,6 @@ void BezierSpline::evaluate_segment(const int index, } } -/** - * Returns access to a cache of offsets into the evaluated point array for each control point. - * While most control point edges generate the number of edges specified by the resolution, vector - * segments only generate one edge. - * - * \note The length of the result is one greater than the number of points, so that the last item - * is the total number of evaluated points. This is useful to avoid recalculating the size of the - * last segment everywhere. - */ Span<int> BezierSpline::control_point_offsets() const { if (!offset_cache_dirty_) { @@ -457,13 +454,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; @@ -503,12 +505,6 @@ static void calculate_mappings_linear_resolution(Span<int> offsets, } } -/** - * Returns non-owning access to an array of values containing the information necessary to - * interpolate values from the original control points to evaluated points. The control point - * index is the integer part of each value, and the factor used for interpolating to the next - * control point is the remaining factional part. - */ Span<float> BezierSpline::evaluated_mappings() const { if (!mapping_cache_dirty_) { @@ -533,7 +529,10 @@ Span<float> BezierSpline::evaluated_mappings() const Span<int> offsets = this->control_point_offsets(); - calculate_mappings_linear_resolution(offsets, size, resolution_, is_cyclic_, mappings); + blender::threading::isolate_task([&]() { + /* Isolate the task, since this is function is multi-threaded and holds a lock. */ + calculate_mappings_linear_resolution(offsets, size, resolution_, is_cyclic_, mappings); + }); mapping_cache_dirty_ = false; return mappings; @@ -550,21 +549,32 @@ 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); - blender::threading::parallel_for(IndexRange(size - 1), grain_size, [&](IndexRange range) { - for (const int i : range) { - this->evaluate_segment(i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i])); - } + blender::threading::isolate_task([&]() { + /* Isolate the task, since this is function is multi-threaded and holds a lock. */ + blender::threading::parallel_for(IndexRange(size - 1), grain_size, [&](IndexRange range) { + for (const int i : range) { + this->evaluate_segment(i, i + 1, positions.slice(offsets[i], offsets[i + 1] - offsets[i])); + } + }); }); if (is_cyclic_) { this->evaluate_segment( @@ -580,11 +590,6 @@ Span<float3> BezierSpline::evaluated_positions() const return positions; } -/** - * Convert the data encoded in #evaulated_mappings into its parts-- the information necessary - * to interpolate data from control points to evaluated points between them. The next control - * point index result will not overflow the size of the control point vectors. - */ BezierSpline::InterpolationData BezierSpline::interpolation_data_from_index_factor( const float index_factor) const { @@ -628,26 +633,26 @@ static void interpolate_to_evaluated_impl(const BezierSpline &spline, } } -GVArrayPtr BezierSpline::interpolate_to_evaluated(const GVArray &src) const +GVArray BezierSpline::interpolate_to_evaluated(const GVArray &src) const { BLI_assert(src.size() == this->size()); if (src.is_single()) { - return src.shallow_copy(); + return src; } const int eval_size = this->evaluated_points_size(); if (eval_size == 1) { - return src.shallow_copy(); + return src; } - GVArrayPtr new_varray; + GVArray new_varray; blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { Array<T> values(eval_size); interpolate_to_evaluated_impl<T>(*this, src.typed<T>(), values); - new_varray = std::make_unique<GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); diff --git a/source/blender/blenkernel/intern/spline_nurbs.cc b/source/blender/blenkernel/intern/spline_nurbs.cc index 6d30d8ba916..5993b9a9a27 100644 --- a/source/blender/blenkernel/intern/spline_nurbs.cc +++ b/source/blender/blenkernel/intern/spline_nurbs.cc @@ -26,10 +26,8 @@ using blender::float3; using blender::IndexRange; using blender::MutableSpan; using blender::Span; +using blender::VArray; using blender::fn::GVArray; -using blender::fn::GVArray_For_ArrayContainer; -using blender::fn::GVArray_Typed; -using blender::fn::GVArrayPtr; void NURBSpline::copy_settings(Spline &dst) const { @@ -83,22 +81,6 @@ void NURBSpline::set_order(const uint8_t value) this->mark_cache_invalid(); } -/** - * \warning Call #reallocate on the spline's attributes after adding all points. - */ -void NURBSpline::add_point(const float3 position, - const float radius, - const float tilt, - const float weight) -{ - positions_.append(position); - radii_.append(radius); - tilts_.append(tilt); - weights_.append(weight); - knots_dirty_ = true; - this->mark_cache_invalid(); -} - void NURBSpline::resize(const int size) { positions_.resize(size); @@ -197,78 +179,48 @@ int NURBSpline::knots_size() const void NURBSpline::calculate_knots() const { const KnotsMode mode = this->knots_mode; - const int length = this->size(); const int order = order_; + const bool is_bezier = mode == NURBSpline::KnotsMode::Bezier; + const bool is_end_point = mode == NURBSpline::KnotsMode::EndPoint; + /* Inner knots are always repeated once except on Bezier case. */ + const int repeat_inner = is_bezier ? order - 1 : 1; + /* How many times to repeat 0.0 at the beginning of knot. */ + const int head = is_end_point && !is_cyclic_ ? order : (is_bezier ? order / 2 : 1); + /* Number of knots replicating widths of the starting knots. + * Covers both Cyclic and EndPoint cases. */ + const int tail = is_cyclic_ ? 2 * order - 1 : (is_end_point ? order : 0); knots_.resize(this->knots_size()); - MutableSpan<float> knots = knots_; - if (mode == NURBSpline::KnotsMode::Normal || is_cyclic_) { - for (const int i : knots.index_range()) { - knots[i] = static_cast<float>(i); - } - } - else if (mode == NURBSpline::KnotsMode::EndPoint) { - float k = 0.0f; - for (const int i : IndexRange(1, knots.size())) { - knots[i - 1] = k; - if (i >= order && i <= length) { - k += 1.0f; - } - } - } - else if (mode == NURBSpline::KnotsMode::Bezier) { - BLI_assert(ELEM(order, 3, 4)); - if (order == 3) { - float k = 0.6f; - for (const int i : knots.index_range()) { - if (i >= order && i <= length) { - k += 0.5f; - } - knots[i] = std::floor(k); - } - } - else { - float k = 0.34f; - for (const int i : knots.index_range()) { - knots[i] = std::floor(k); - k += 1.0f / 3.0f; - } - } - } + int r = head; + float current = 0.0f; - if (is_cyclic_) { - const int b = length + order - 1; - if (order > 2) { - for (const int i : IndexRange(1, order - 2)) { - if (knots[b] != knots[b - i]) { - if (i == order - 1) { - knots[length + order - 2] += 1.0f; - break; - } - } - } + for (const int i : IndexRange(knots.size() - tail)) { + knots[i] = current; + r--; + if (r == 0) { + current += 1.0; + r = repeat_inner; } + } - int c = order; - for (int i = b; i < this->knots_size(); i++) { - knots[i] = knots[i - 1] + (knots[c] - knots[c - 1]); - c--; - } + const int tail_index = knots.size() - tail; + for (const int i : IndexRange(tail)) { + knots[tail_index + i] = current + (knots[i] - knots[0]); } } Span<float> NURBSpline::knots() const { if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } std::lock_guard lock{knots_mutex_}; if (!knots_dirty_) { - BLI_assert(knots_.size() == this->size() + order_); + BLI_assert(knots_.size() == this->knots_size()); return knots_; } @@ -410,23 +362,23 @@ void interpolate_to_evaluated_impl(Span<NURBSpline::BasisCache> weights, mixer.finalize(); } -GVArrayPtr NURBSpline::interpolate_to_evaluated(const GVArray &src) const +GVArray NURBSpline::interpolate_to_evaluated(const GVArray &src) const { BLI_assert(src.size() == this->size()); if (src.is_single()) { - return src.shallow_copy(); + return src; } Span<BasisCache> basis_cache = this->calculate_basis_cache(); - GVArrayPtr new_varray; + GVArray new_varray; blender::attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { using T = decltype(dummy); if constexpr (!std::is_void_v<blender::attribute_math::DefaultMixer<T>>) { Array<T> values(this->evaluated_points_size()); interpolate_to_evaluated_impl<T>(basis_cache, src.typed<T>(), values); - new_varray = std::make_unique<GVArray_For_ArrayContainer<Array<T>>>(std::move(values)); + new_varray = VArray<T>::ForContainer(std::move(values)); } }); @@ -448,8 +400,8 @@ Span<float3> NURBSpline::evaluated_positions() const evaluated_position_cache_.resize(eval_size); /* TODO: Avoid copying the evaluated data from the temporary array. */ - GVArray_Typed<float3> evaluated = Spline::interpolate_to_evaluated(positions_.as_span()); - evaluated->materialize(evaluated_position_cache_); + VArray<float3> evaluated = Spline::interpolate_to_evaluated(positions_.as_span()); + evaluated.materialize(evaluated_position_cache_); position_cache_dirty_ = false; return evaluated_position_cache_; diff --git a/source/blender/blenkernel/intern/spline_poly.cc b/source/blender/blenkernel/intern/spline_poly.cc index 338b5d0ac9e..480bbd1dfe8 100644 --- a/source/blender/blenkernel/intern/spline_poly.cc +++ b/source/blender/blenkernel/intern/spline_poly.cc @@ -23,7 +23,6 @@ using blender::float3; using blender::MutableSpan; using blender::Span; using blender::fn::GVArray; -using blender::fn::GVArrayPtr; void PolySpline::copy_settings(Spline &UNUSED(dst)) const { @@ -46,17 +45,6 @@ int PolySpline::size() const return size; } -/** - * \warning Call #reallocate on the spline's attributes after adding all points. - */ -void PolySpline::add_point(const float3 position, const float radius, const float tilt) -{ - positions_.append(position); - radii_.append(radius); - tilts_.append(tilt); - this->mark_cache_invalid(); -} - void PolySpline::resize(const int size) { positions_.resize(size); @@ -116,15 +104,8 @@ Span<float3> PolySpline::evaluated_positions() const return this->positions(); } -/** - * Poly spline interpolation from control points to evaluated points is a special case, since - * the result data is the same as the input data. This function returns a GVArray that points to - * the original data. Therefore the lifetime of the returned virtual array must not be longer than - * the source data. - */ -GVArrayPtr PolySpline::interpolate_to_evaluated(const GVArray &src) const +GVArray PolySpline::interpolate_to_evaluated(const GVArray &src) const { BLI_assert(src.size() == this->size()); - - return src.shallow_copy(); + return src; } diff --git a/source/blender/blenkernel/intern/studiolight.c b/source/blender/blenkernel/intern/studiolight.c index 29f726ece71..b7690e69aa1 100644 --- a/source/blender/blenkernel/intern/studiolight.c +++ b/source/blender/blenkernel/intern/studiolight.c @@ -1402,7 +1402,6 @@ void BKE_studiolight_default(SolidLight lights[4], float light_ambient[3]) lights[3].vec[2] = -0.542269f; } -/* API */ void BKE_studiolight_init(void) { /* Add default studio light */ @@ -1532,7 +1531,6 @@ void BKE_studiolight_preview(uint *icon_buffer, StudioLight *sl, int icon_id_typ } } -/* Ensure state of Studiolights */ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag) { if ((sl->flag & flag) == flag) { @@ -1570,8 +1568,9 @@ void BKE_studiolight_ensure_flag(StudioLight *sl, int flag) } /* - * Python API Functions + * Python API Functions. */ + void BKE_studiolight_remove(StudioLight *sl) { if (sl->flag & STUDIOLIGHT_USER_DEFINED) { @@ -1608,7 +1607,6 @@ StudioLight *BKE_studiolight_create(const char *path, return sl; } -/* Only useful for workbench while editing the userprefs. */ StudioLight *BKE_studiolight_studio_edit_get(void) { static StudioLight sl = {0}; diff --git a/source/blender/blenkernel/intern/subdiv.c b/source/blender/blenkernel/intern/subdiv.c index fd32f52351a..45810e29565 100644 --- a/source/blender/blenkernel/intern/subdiv.c +++ b/source/blender/blenkernel/intern/subdiv.c @@ -29,6 +29,9 @@ #include "BLI_utildefines.h" +#include "BKE_modifier.h" +#include "BKE_subdiv_modifier.h" + #include "MEM_guardedalloc.h" #include "subdiv_converter.h" @@ -189,6 +192,12 @@ Subdiv *BKE_subdiv_update_from_mesh(Subdiv *subdiv, void BKE_subdiv_free(Subdiv *subdiv) { if (subdiv->evaluator != NULL) { + const eOpenSubdivEvaluator evaluator_type = subdiv->evaluator->type; + if (evaluator_type != OPENSUBDIV_EVALUATOR_CPU) { + /* Let the draw code do the freeing, to ensure that the OpenGL context is valid. */ + BKE_subsurf_modifier_free_gpu_cache_cb(subdiv); + return; + } openSubdiv_deleteEvaluator(subdiv->evaluator); } if (subdiv->topology_refiner != NULL) { @@ -214,12 +223,13 @@ int *BKE_subdiv_face_ptex_offset_get(Subdiv *subdiv) } const int num_coarse_faces = topology_refiner->getNumFaces(topology_refiner); subdiv->cache_.face_ptex_offset = MEM_malloc_arrayN( - num_coarse_faces, sizeof(int), "subdiv face_ptex_offset"); + num_coarse_faces + 1, sizeof(int), "subdiv face_ptex_offset"); int ptex_offset = 0; for (int face_index = 0; face_index < num_coarse_faces; face_index++) { const int num_ptex_faces = topology_refiner->getNumFacePtexFaces(topology_refiner, face_index); subdiv->cache_.face_ptex_offset[face_index] = ptex_offset; ptex_offset += num_ptex_faces; } + subdiv->cache_.face_ptex_offset[num_coarse_faces] = ptex_offset; return subdiv->cache_.face_ptex_offset; } diff --git a/source/blender/blenkernel/intern/subdiv_ccg.c b/source/blender/blenkernel/intern/subdiv_ccg.c index 95f51a72b70..7d876acf776 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; } @@ -604,7 +603,8 @@ Mesh *BKE_subdiv_to_ccg_mesh(Subdiv *subdiv, { /* Make sure evaluator is ready. */ BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_CCG); - if (!BKE_subdiv_eval_begin_from_mesh(subdiv, coarse_mesh, NULL)) { + if (!BKE_subdiv_eval_begin_from_mesh( + subdiv, coarse_mesh, NULL, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) { if (coarse_mesh->totpoly) { return NULL; } @@ -1062,7 +1062,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++) { @@ -1797,7 +1797,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 +1972,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_mesh.c b/source/blender/blenkernel/intern/subdiv_converter_mesh.c index 41fc28c5d52..fc7ef887879 100644 --- a/source/blender/blenkernel/intern/subdiv_converter_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_converter_mesh.c @@ -40,6 +40,8 @@ #include "opensubdiv_capi.h" #include "opensubdiv_converter_capi.h" +#include "bmesh_class.h" + /* Enable work-around for non-working CPU evaluator when using bilinear scheme. * This forces Catmark scheme with all edges marked as infinitely sharp. */ #define BUGGY_SIMPLE_SCHEME_WORKAROUND 1 @@ -47,6 +49,8 @@ typedef struct ConverterStorage { SubdivSettings settings; const Mesh *mesh; + /* CustomData layer for vertex sharpnesses. */ + const float *cd_vertex_crease; /* Indexed by loop index, value denotes index of face-varying vertex * which corresponds to the UV coordinate. */ @@ -168,7 +172,7 @@ static float get_edge_sharpness(const OpenSubdiv_Converter *converter, int manif } const int edge_index = storage->manifold_edge_index_reverse[manifold_edge_index]; const MEdge *medge = storage->mesh->medge; - return BKE_subdiv_edge_crease_to_sharpness_char(medge[edge_index].crease); + return BKE_subdiv_crease_to_sharpness_char(medge[edge_index].crease); } static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter, @@ -184,14 +188,14 @@ static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter, return BLI_BITMAP_TEST_BOOL(storage->infinite_sharp_vertices_map, vertex_index); } -static float get_vertex_sharpness(const OpenSubdiv_Converter *converter, - int UNUSED(manifold_vertex_index)) +static float get_vertex_sharpness(const OpenSubdiv_Converter *converter, int manifold_vertex_index) { ConverterStorage *storage = converter->user_data; - if (!storage->settings.use_creases) { + if (!storage->settings.use_creases || storage->cd_vertex_crease == NULL) { return 0.0f; } - return 0.0f; + const int vertex_index = storage->manifold_vertex_index_reverse[manifold_vertex_index]; + return BKE_subdiv_crease_to_sharpness_f(storage->cd_vertex_crease[vertex_index]); } static int get_num_uv_layers(const OpenSubdiv_Converter *converter) @@ -393,6 +397,7 @@ static void init_user_data(OpenSubdiv_Converter *converter, ConverterStorage *user_data = MEM_mallocN(sizeof(ConverterStorage), __func__); user_data->settings = *settings; user_data->mesh = mesh; + user_data->cd_vertex_crease = CustomData_get_layer(&mesh->vdata, CD_CREASE); user_data->loop_uv_indices = NULL; initialize_manifold_indices(user_data); converter->user_data = user_data; diff --git a/source/blender/blenkernel/intern/subdiv_deform.c b/source/blender/blenkernel/intern/subdiv_deform.c index 2c900fbd600..c385b1b291d 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) @@ -117,7 +117,8 @@ static bool subdiv_mesh_topology_info(const SubdivForeachContext *foreach_contex const int UNUSED(num_vertices), const int UNUSED(num_edges), const int UNUSED(num_loops), - const int UNUSED(num_polygons)) + const int UNUSED(num_polygons), + const int *UNUSED(subdiv_polygon_offset)) { SubdivDeformContext *subdiv_context = foreach_context->user_data; subdiv_mesh_prepare_accumulator(subdiv_context, subdiv_context->coarse_mesh->totvert); @@ -202,7 +203,8 @@ void BKE_subdiv_deform_coarse_vertices(struct Subdiv *subdiv, BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH); /* Make sure evaluator is up to date with possible new topology, and that * is refined for the new positions of coarse vertices. */ - if (!BKE_subdiv_eval_begin_from_mesh(subdiv, coarse_mesh, vertex_cos)) { + if (!BKE_subdiv_eval_begin_from_mesh( + subdiv, coarse_mesh, vertex_cos, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) { /* This could happen in two situations: * - OpenSubdiv is disabled. * - Something totally bad happened, and OpenSubdiv rejected our diff --git a/source/blender/blenkernel/intern/subdiv_eval.c b/source/blender/blenkernel/intern/subdiv_eval.c index 0001eb8a205..c2f7581637b 100644 --- a/source/blender/blenkernel/intern/subdiv_eval.c +++ b/source/blender/blenkernel/intern/subdiv_eval.c @@ -28,6 +28,7 @@ #include "BLI_bitmap.h" #include "BLI_math_vector.h" +#include "BLI_task.h" #include "BLI_utildefines.h" #include "BKE_customdata.h" @@ -38,7 +39,28 @@ #include "opensubdiv_evaluator_capi.h" #include "opensubdiv_topology_refiner_capi.h" -bool BKE_subdiv_eval_begin(Subdiv *subdiv) +/* ============================ Helper Function ============================ */ + +static eOpenSubdivEvaluator opensubdiv_evalutor_from_subdiv_evaluator_type( + eSubdivEvaluatorType evaluator_type) +{ + switch (evaluator_type) { + case SUBDIV_EVALUATOR_TYPE_CPU: { + return OPENSUBDIV_EVALUATOR_CPU; + } + case SUBDIV_EVALUATOR_TYPE_GLSL_COMPUTE: { + return OPENSUBDIV_EVALUATOR_GLSL_COMPUTE; + } + } + BLI_assert_msg(0, "Unknown evaluator type"); + return OPENSUBDIV_EVALUATOR_CPU; +} + +/* ====================== Main Subdivision Evaluation ====================== */ + +bool BKE_subdiv_eval_begin(Subdiv *subdiv, + eSubdivEvaluatorType evaluator_type, + OpenSubdiv_EvaluatorCache *evaluator_cache) { BKE_subdiv_stats_reset(&subdiv->stats, SUBDIV_STATS_EVALUATOR_CREATE); if (subdiv->topology_refiner == NULL) { @@ -47,8 +69,11 @@ bool BKE_subdiv_eval_begin(Subdiv *subdiv) return false; } if (subdiv->evaluator == NULL) { + eOpenSubdivEvaluator opensubdiv_evaluator_type = + opensubdiv_evalutor_from_subdiv_evaluator_type(evaluator_type); BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_EVALUATOR_CREATE); - subdiv->evaluator = openSubdiv_createEvaluatorFromTopologyRefiner(subdiv->topology_refiner); + subdiv->evaluator = openSubdiv_createEvaluatorFromTopologyRefiner( + subdiv->topology_refiner, opensubdiv_evaluator_type, evaluator_cache); BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_EVALUATOR_CREATE); if (subdiv->evaluator == NULL) { return false; @@ -80,6 +105,9 @@ static void set_coarse_positions(Subdiv *subdiv, BLI_BITMAP_ENABLE(vertex_used_map, loop->v); } } + /* Use a temporary buffer so we do not upload vertices one at a time to the GPU. */ + float(*buffer)[3] = MEM_mallocN(sizeof(float[3]) * mesh->totvert, "subdiv tmp coarse positions"); + int manifold_vertex_count = 0; for (int vertex_index = 0, manifold_vertex_index = 0; vertex_index < mesh->totvert; vertex_index++) { if (!BLI_BITMAP_TEST_BOOL(vertex_used_map, vertex_index)) { @@ -93,13 +121,49 @@ static void set_coarse_positions(Subdiv *subdiv, const MVert *vertex = &mvert[vertex_index]; vertex_co = vertex->co; } - subdiv->evaluator->setCoarsePositions(subdiv->evaluator, vertex_co, manifold_vertex_index, 1); + copy_v3_v3(&buffer[manifold_vertex_index][0], vertex_co); manifold_vertex_index++; + manifold_vertex_count++; } + subdiv->evaluator->setCoarsePositions( + subdiv->evaluator, &buffer[0][0], 0, manifold_vertex_count); MEM_freeN(vertex_used_map); + MEM_freeN(buffer); +} + +/* Context which is used to fill face varying data in parallel. */ +typedef struct FaceVaryingDataFromUVContext { + OpenSubdiv_TopologyRefiner *topology_refiner; + const Mesh *mesh; + const MLoopUV *mloopuv; + float (*buffer)[2]; + int layer_index; +} FaceVaryingDataFromUVContext; + +static void set_face_varying_data_from_uv_task(void *__restrict userdata, + const int face_index, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + FaceVaryingDataFromUVContext *ctx = userdata; + OpenSubdiv_TopologyRefiner *topology_refiner = ctx->topology_refiner; + const int layer_index = ctx->layer_index; + const Mesh *mesh = ctx->mesh; + const MPoly *mpoly = &mesh->mpoly[face_index]; + const MLoopUV *mluv = &ctx->mloopuv[mpoly->loopstart]; + + /* TODO(sergey): OpenSubdiv's C-API converter can change winding of + * loops of a face, need to watch for that, to prevent wrong UVs assigned. + */ + const int num_face_vertices = topology_refiner->getNumFaceVertices(topology_refiner, face_index); + const int *uv_indices = topology_refiner->getFaceFVarValueIndices( + topology_refiner, face_index, layer_index); + for (int vertex_index = 0; vertex_index < num_face_vertices; vertex_index++, mluv++) { + copy_v2_v2(ctx->buffer[uv_indices[vertex_index]], mluv->uv); + } } static void set_face_varying_data_from_uv(Subdiv *subdiv, + const Mesh *mesh, const MLoopUV *mloopuv, const int layer_index) { @@ -107,25 +171,37 @@ static void set_face_varying_data_from_uv(Subdiv *subdiv, OpenSubdiv_Evaluator *evaluator = subdiv->evaluator; const int num_faces = topology_refiner->getNumFaces(topology_refiner); const MLoopUV *mluv = mloopuv; - /* TODO(sergey): OpenSubdiv's C-API converter can change winding of - * loops of a face, need to watch for that, to prevent wrong UVs assigned. - */ - for (int face_index = 0; face_index < num_faces; face_index++) { - const int num_face_vertices = topology_refiner->getNumFaceVertices(topology_refiner, - face_index); - const int *uv_indices = topology_refiner->getFaceFVarValueIndices( - topology_refiner, face_index, layer_index); - for (int vertex_index = 0; vertex_index < num_face_vertices; vertex_index++, mluv++) { - evaluator->setFaceVaryingData(evaluator, layer_index, mluv->uv, uv_indices[vertex_index], 1); - } - } + + const int num_fvar_values = topology_refiner->getNumFVarValues(topology_refiner, layer_index); + /* Use a temporary buffer so we do not upload UVs one at a time to the GPU. */ + float(*buffer)[2] = MEM_mallocN(sizeof(float[2]) * num_fvar_values, "temp UV storage"); + + FaceVaryingDataFromUVContext ctx; + ctx.topology_refiner = topology_refiner; + ctx.layer_index = layer_index; + ctx.mloopuv = mluv; + ctx.mesh = mesh; + ctx.buffer = buffer; + + TaskParallelSettings parallel_range_settings; + BLI_parallel_range_settings_defaults(¶llel_range_settings); + parallel_range_settings.min_iter_per_thread = 1; + + BLI_task_parallel_range( + 0, num_faces, &ctx, set_face_varying_data_from_uv_task, ¶llel_range_settings); + + evaluator->setFaceVaryingData(evaluator, layer_index, &buffer[0][0], 0, num_fvar_values); + + MEM_freeN(buffer); } bool BKE_subdiv_eval_begin_from_mesh(Subdiv *subdiv, const Mesh *mesh, - const float (*coarse_vertex_cos)[3]) + const float (*coarse_vertex_cos)[3], + eSubdivEvaluatorType evaluator_type, + OpenSubdiv_EvaluatorCache *evaluator_cache) { - if (!BKE_subdiv_eval_begin(subdiv)) { + if (!BKE_subdiv_eval_begin(subdiv, evaluator_type, evaluator_cache)) { return false; } return BKE_subdiv_eval_refine_from_mesh(subdiv, mesh, coarse_vertex_cos); @@ -146,7 +222,7 @@ bool BKE_subdiv_eval_refine_from_mesh(Subdiv *subdiv, const int num_uv_layers = CustomData_number_of_layers(&mesh->ldata, CD_MLOOPUV); for (int layer_index = 0; layer_index < num_uv_layers; layer_index++) { const MLoopUV *mloopuv = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPUV, layer_index); - set_face_varying_data_from_uv(subdiv, mloopuv, layer_index); + set_face_varying_data_from_uv(subdiv, mesh, mloopuv, layer_index); } /* Update evaluator to the new coarse geometry. */ BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_EVALUATOR_REFINE); @@ -188,8 +264,8 @@ void BKE_subdiv_eval_limit_point_and_derivatives(Subdiv *subdiv, * This happens, for example, in single vertex on Suzannne's nose (where two quads have 2 common * edges). * - * This makes tangent space displacement (such as multires) impossible to be used in those - * vertices, so those needs to be addressed in one way or another. + * This makes tangent space displacement (such as multi-resolution) impossible to be used in + * those vertices, so those needs to be addressed in one way or another. * * Simplest thing to do: step inside of the face a little bit, where there is known patch at * which there must be proper derivatives. This might break continuity of normals, but is better @@ -221,18 +297,6 @@ void BKE_subdiv_eval_limit_point_and_normal(Subdiv *subdiv, normalize_v3(r_N); } -void BKE_subdiv_eval_limit_point_and_short_normal(Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, - float r_P[3], - short r_N[3]) -{ - float N_float[3]; - BKE_subdiv_eval_limit_point_and_normal(subdiv, ptex_face_index, u, v, r_P, N_float); - normal_float_to_short_v3(r_N, N_float); -} - void BKE_subdiv_eval_face_varying(Subdiv *subdiv, const int face_varying_channel, const int ptex_face_index, @@ -273,125 +337,3 @@ void BKE_subdiv_eval_final_point( BKE_subdiv_eval_limit_point(subdiv, ptex_face_index, u, v, r_P); } } - -/* =================== Patch queries at given resolution =================== */ - -/* Move buffer forward by a given number of bytes. */ -static void buffer_apply_offset(void **buffer, const int offset) -{ - *buffer = ((unsigned char *)*buffer) + offset; -} - -/* Write given number of floats to the beginning of given buffer. */ -static void buffer_write_float_value(void **buffer, const float *values_buffer, int num_values) -{ - memcpy(*buffer, values_buffer, sizeof(float) * num_values); -} - -/* Similar to above, just operates with short values. */ -static void buffer_write_short_value(void **buffer, const short *values_buffer, int num_values) -{ - memcpy(*buffer, values_buffer, sizeof(short) * num_values); -} - -void BKE_subdiv_eval_limit_patch_resolution_point(Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *buffer, - const int offset, - const int stride) -{ - buffer_apply_offset(&buffer, offset); - const float inv_resolution_1 = 1.0f / (float)(resolution - 1); - for (int y = 0; y < resolution; y++) { - const float v = y * inv_resolution_1; - for (int x = 0; x < resolution; x++) { - const float u = x * inv_resolution_1; - BKE_subdiv_eval_limit_point(subdiv, ptex_face_index, u, v, buffer); - buffer_apply_offset(&buffer, stride); - } - } -} - -void BKE_subdiv_eval_limit_patch_resolution_point_and_derivatives(Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *du_buffer, - const int du_offset, - const int du_stride, - void *dv_buffer, - const int dv_offset, - const int dv_stride) -{ - buffer_apply_offset(&point_buffer, point_offset); - buffer_apply_offset(&du_buffer, du_offset); - buffer_apply_offset(&dv_buffer, dv_offset); - const float inv_resolution_1 = 1.0f / (float)(resolution - 1); - for (int y = 0; y < resolution; y++) { - const float v = y * inv_resolution_1; - for (int x = 0; x < resolution; x++) { - const float u = x * inv_resolution_1; - BKE_subdiv_eval_limit_point_and_derivatives( - subdiv, ptex_face_index, u, v, point_buffer, du_buffer, dv_buffer); - buffer_apply_offset(&point_buffer, point_stride); - buffer_apply_offset(&du_buffer, du_stride); - buffer_apply_offset(&dv_buffer, dv_stride); - } - } -} - -void BKE_subdiv_eval_limit_patch_resolution_point_and_normal(Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *normal_buffer, - const int normal_offset, - const int normal_stride) -{ - buffer_apply_offset(&point_buffer, point_offset); - buffer_apply_offset(&normal_buffer, normal_offset); - const float inv_resolution_1 = 1.0f / (float)(resolution - 1); - for (int y = 0; y < resolution; y++) { - const float v = y * inv_resolution_1; - for (int x = 0; x < resolution; x++) { - const float u = x * inv_resolution_1; - float normal[3]; - BKE_subdiv_eval_limit_point_and_normal(subdiv, ptex_face_index, u, v, point_buffer, normal); - buffer_write_float_value(&normal_buffer, normal, 3); - buffer_apply_offset(&point_buffer, point_stride); - buffer_apply_offset(&normal_buffer, normal_stride); - } - } -} - -void BKE_subdiv_eval_limit_patch_resolution_point_and_short_normal(Subdiv *subdiv, - const int ptex_face_index, - const int resolution, - void *point_buffer, - const int point_offset, - const int point_stride, - void *normal_buffer, - const int normal_offset, - const int normal_stride) -{ - buffer_apply_offset(&point_buffer, point_offset); - buffer_apply_offset(&normal_buffer, normal_offset); - const float inv_resolution_1 = 1.0f / (float)(resolution - 1); - for (int y = 0; y < resolution; y++) { - const float v = y * inv_resolution_1; - for (int x = 0; x < resolution; x++) { - const float u = x * inv_resolution_1; - short normal[3]; - BKE_subdiv_eval_limit_point_and_short_normal( - subdiv, ptex_face_index, u, v, point_buffer, normal); - buffer_write_short_value(&normal_buffer, normal, 3); - buffer_apply_offset(&point_buffer, point_stride); - buffer_apply_offset(&normal_buffer, normal_stride); - } - } -} diff --git a/source/blender/blenkernel/intern/subdiv_foreach.c b/source/blender/blenkernel/intern/subdiv_foreach.c index 061c196df2a..69bead27fe6 100644 --- a/source/blender/blenkernel/intern/subdiv_foreach.c +++ b/source/blender/blenkernel/intern/subdiv_foreach.c @@ -1877,7 +1877,8 @@ bool BKE_subdiv_foreach_subdiv_geometry(Subdiv *subdiv, ctx.num_subdiv_vertices, ctx.num_subdiv_edges, ctx.num_subdiv_loops, - ctx.num_subdiv_polygons)) { + ctx.num_subdiv_polygons, + ctx.subdiv_polygon_offset)) { subdiv_foreach_ctx_free(&ctx); return false; } diff --git a/source/blender/blenkernel/intern/subdiv_inline.h b/source/blender/blenkernel/intern/subdiv_inline.h index ba45d0a4997..d52adff1e61 100644 --- a/source/blender/blenkernel/intern/subdiv_inline.h +++ b/source/blender/blenkernel/intern/subdiv_inline.h @@ -103,13 +103,13 @@ BLI_INLINE void BKE_subdiv_rotate_grid_to_quad( } } -BLI_INLINE float BKE_subdiv_edge_crease_to_sharpness_f(float edge_crease) +BLI_INLINE float BKE_subdiv_crease_to_sharpness_f(float edge_crease) { return edge_crease * edge_crease * 10.0f; } -BLI_INLINE float BKE_subdiv_edge_crease_to_sharpness_char(char edge_crease) +BLI_INLINE float BKE_subdiv_crease_to_sharpness_char(char edge_crease) { const float edge_crease_f = edge_crease / 255.0f; - return BKE_subdiv_edge_crease_to_sharpness_f(edge_crease_f); + return BKE_subdiv_crease_to_sharpness_f(edge_crease_f); } diff --git a/source/blender/blenkernel/intern/subdiv_mesh.c b/source/blender/blenkernel/intern/subdiv_mesh.c index 01bccab1bbd..c334d9a2c33 100644 --- a/source/blender/blenkernel/intern/subdiv_mesh.c +++ b/source/blender/blenkernel/intern/subdiv_mesh.c @@ -21,6 +21,7 @@ * \ingroup bke */ +#include "BKE_mesh.h" #include "BKE_subdiv_mesh.h" #include "atomic_ops.h" @@ -58,23 +59,8 @@ typedef struct SubdivMeshContext { /* UV layers interpolation. */ int num_uv_layers; MLoopUV *uv_layers[MAX_MTFACE]; - /* Accumulated values. - * - * Averaging is happening for vertices along the coarse edges and corners. - * This is needed for both displacement and normals. - * - * Displacement is being accumulated to a vertices coordinates, since those - * are not needed during traversal of edge/corner vertices. - * - * For normals we are using dedicated array, since we can not use same - * vertices (normals are `short`, which will cause a lot of precision - * issues). */ - float (*accumulated_normals)[3]; /* Per-subdivided vertex counter of averaged values. */ int *accumulated_counters; - /* Denotes whether normals can be evaluated from a limit surface. One case - * when it's not possible is when displacement is used. */ - bool can_evaluate_normals; bool have_displacement; } SubdivMeshContext; @@ -102,20 +88,12 @@ static void subdiv_mesh_ctx_cache_custom_data_layers(SubdivMeshContext *ctx) static void subdiv_mesh_prepare_accumulator(SubdivMeshContext *ctx, int num_vertices) { - if (!ctx->can_evaluate_normals && !ctx->have_displacement) { - return; - } - /* 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"); 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) { - MEM_SAFE_FREE(ctx->accumulated_normals); MEM_SAFE_FREE(ctx->accumulated_counters); } @@ -450,48 +428,23 @@ static void subdiv_mesh_tls_free(void *tls_v) /** \} */ -/* -------------------------------------------------------------------- */ -/** \name Evaluation helper functions - * \{ */ - -static void eval_final_point_and_vertex_normal(Subdiv *subdiv, - const int ptex_face_index, - const float u, - const float v, - float r_P[3], - short r_N[3]) -{ - if (subdiv->displacement_evaluator == NULL) { - BKE_subdiv_eval_limit_point_and_short_normal(subdiv, ptex_face_index, u, v, r_P, r_N); - } - else { - BKE_subdiv_eval_final_point(subdiv, ptex_face_index, u, v, r_P); - } -} - /** \} */ /* -------------------------------------------------------------------- */ /** \name Accumulation helpers * \{ */ -static void subdiv_accumulate_vertex_normal_and_displacement(SubdivMeshContext *ctx, - const int ptex_face_index, - const float u, - const float v, - MVert *subdiv_vert) +static void subdiv_accumulate_vertex_displacement(SubdivMeshContext *ctx, + const int ptex_face_index, + const float u, + const float v, + MVert *subdiv_vert) { Subdiv *subdiv = ctx->subdiv; const int subdiv_vertex_index = subdiv_vert - ctx->subdiv_mesh->mvert; float dummy_P[3], dPdu[3], dPdv[3], D[3]; BKE_subdiv_eval_limit_point_and_derivatives(subdiv, ptex_face_index, u, v, dummy_P, dPdu, dPdv); - /* Accumulate normal. */ - if (ctx->can_evaluate_normals) { - float N[3]; - cross_v3_v3v3(N, dPdu, dPdv); - normalize_v3(N); - add_v3_v3(ctx->accumulated_normals[subdiv_vertex_index], N); - } + /* Accumulate displacement if needed. */ if (ctx->have_displacement) { /* NOTE: The subdivided mesh is allocated in this module, and its vertices are kept at zero @@ -514,9 +467,10 @@ static bool subdiv_mesh_topology_info(const SubdivForeachContext *foreach_contex const int num_vertices, const int num_edges, const int num_loops, - const int num_polygons) + const int num_polygons, + const int *UNUSED(subdiv_polygon_offset)) { - /* Multires grid data will be applied or become invalid after subdivision, + /* Multi-resolution grid data will be applied or become invalid after subdivision, * so don't try to preserve it and use memory. */ CustomData_MeshMasks mask = CD_MASK_EVERYTHING; mask.lmask &= ~CD_MASK_MULTIRES_GRIDS; @@ -588,13 +542,6 @@ static void evaluate_vertex_and_apply_displacement_copy(const SubdivMeshContext BKE_subdiv_eval_limit_point(ctx->subdiv, ptex_face_index, u, v, subdiv_vert->co); /* Apply displacement. */ add_v3_v3(subdiv_vert->co, D); - /* Copy normal from accumulated storage. */ - if (ctx->can_evaluate_normals) { - float N[3]; - copy_v3_v3(N, ctx->accumulated_normals[subdiv_vertex_index]); - normalize_v3(N); - normal_float_to_short_v3(subdiv_vert->no, N); - } /* Remove facedot flag. This can happen if there is more than one subsurf modifier. */ subdiv_vert->flag &= ~ME_VERT_FACEDOT; } @@ -621,15 +568,6 @@ static void evaluate_vertex_and_apply_displacement_interpolate( BKE_subdiv_eval_limit_point(ctx->subdiv, ptex_face_index, u, v, subdiv_vert->co); /* Apply displacement. */ add_v3_v3(subdiv_vert->co, D); - /* Copy normal from accumulated storage. */ - if (ctx->can_evaluate_normals) { - const float inv_num_accumulated = 1.0f / ctx->accumulated_counters[subdiv_vertex_index]; - float N[3]; - copy_v3_v3(N, ctx->accumulated_normals[subdiv_vertex_index]); - mul_v3_fl(N, inv_num_accumulated); - normalize_v3(N); - normal_float_to_short_v3(subdiv_vert->no, N); - } } static void subdiv_mesh_vertex_every_corner_or_edge(const SubdivForeachContext *foreach_context, @@ -643,7 +581,7 @@ static void subdiv_mesh_vertex_every_corner_or_edge(const SubdivForeachContext * Mesh *subdiv_mesh = ctx->subdiv_mesh; MVert *subdiv_mvert = subdiv_mesh->mvert; MVert *subdiv_vert = &subdiv_mvert[subdiv_vertex_index]; - subdiv_accumulate_vertex_normal_and_displacement(ctx, ptex_face_index, u, v, subdiv_vert); + subdiv_accumulate_vertex_displacement(ctx, ptex_face_index, u, v, subdiv_vert); } static void subdiv_mesh_vertex_every_corner(const SubdivForeachContext *foreach_context, @@ -792,8 +730,7 @@ static void subdiv_mesh_vertex_inner(const SubdivForeachContext *foreach_context MVert *subdiv_vert = &subdiv_mvert[subdiv_vertex_index]; subdiv_mesh_ensure_vertex_interpolation(ctx, tls, coarse_poly, coarse_corner); subdiv_vertex_data_interpolate(ctx, subdiv_vert, &tls->vertex_interpolation, u, v); - eval_final_point_and_vertex_normal( - subdiv, ptex_face_index, u, v, subdiv_vert->co, subdiv_vert->no); + BKE_subdiv_eval_final_point(subdiv, ptex_face_index, u, v, subdiv_vert->co); subdiv_mesh_tag_center_vertex(coarse_poly, subdiv_vert, u, v); } @@ -1083,29 +1020,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 +1052,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 (!ELEM(u, 0.0, 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) { @@ -1146,12 +1077,6 @@ static void subdiv_mesh_vertex_of_loose_edge(const struct SubdivForeachContext * /* TODO(sergey): This matches old behavior, but we can as well interpolate * it. Maybe even using vertex varying attributes. */ subdiv_vertex->bweight = 0.0f; - /* Reset normal, initialize it in a similar way as edit mode does for a - * vertices adjacent to a loose edges. - * See `mesh_evaluate#mesh_calc_normals_vert_fallback` */ - float no[3]; - normalize_v3_v3(no, subdiv_vertex->co); - normal_float_to_short_v3(subdiv_vertex->no, no); } /** \} */ @@ -1166,8 +1091,8 @@ static void setup_foreach_callbacks(const SubdivMeshContext *subdiv_context, memset(foreach_context, 0, sizeof(*foreach_context)); /* General information. */ foreach_context->topology_info = subdiv_mesh_topology_info; - /* Every boundary geometry. Used for displacement and normals averaging. */ - if (subdiv_context->can_evaluate_normals || subdiv_context->have_displacement) { + /* Every boundary geometry. Used for displacement averaging. */ + if (subdiv_context->have_displacement) { foreach_context->vertex_every_corner = subdiv_mesh_vertex_every_corner; foreach_context->vertex_every_edge = subdiv_mesh_vertex_every_edge; } @@ -1199,7 +1124,8 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv, BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH); /* Make sure evaluator is up to date with possible new topology, and that * it is refined for the new positions of coarse vertices. */ - if (!BKE_subdiv_eval_begin_from_mesh(subdiv, coarse_mesh, NULL)) { + if (!BKE_subdiv_eval_begin_from_mesh( + subdiv, coarse_mesh, NULL, SUBDIV_EVALUATOR_TYPE_CPU, NULL)) { /* This could happen in two situations: * - OpenSubdiv is disabled. * - Something totally bad happened, and OpenSubdiv rejected our @@ -1216,8 +1142,6 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv, subdiv_context.coarse_mesh = coarse_mesh; subdiv_context.subdiv = subdiv; subdiv_context.have_displacement = (subdiv->displacement_evaluator != NULL); - subdiv_context.can_evaluate_normals = !subdiv_context.have_displacement && - subdiv_context.subdiv->settings.is_adaptive; /* Multi-threaded traversal/evaluation. */ BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH_GEOMETRY); SubdivForeachContext foreach_context; @@ -1231,9 +1155,11 @@ Mesh *BKE_subdiv_to_mesh(Subdiv *subdiv, Mesh *result = subdiv_context.subdiv_mesh; // BKE_mesh_validate(result, true, true); BKE_subdiv_stats_end(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_MESH); - if (!subdiv_context.can_evaluate_normals) { - BKE_mesh_normals_tag_dirty(result); - } + /* Using normals from the limit surface gives different results than Blender's vertex normal + * calculation. Since vertex normals are supposed to be a consistent cache, don't bother + * calculating them here. The work may have been pointless anyway if the mesh is deformed or + * changed afterwards. */ + BKE_mesh_normals_tag_dirty(result); /* Free used memory. */ subdiv_mesh_context_free(&subdiv_context); return result; diff --git a/source/blender/blenkernel/intern/subdiv_modifier.c b/source/blender/blenkernel/intern/subdiv_modifier.c new file mode 100644 index 00000000000..525c4837bc4 --- /dev/null +++ b/source/blender/blenkernel/intern/subdiv_modifier.c @@ -0,0 +1,160 @@ +/* + * 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 by Blender Foundation. + * All rights reserved. + */ + +#include "BKE_subdiv_modifier.h" + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_modifier_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_modifier.h" +#include "BKE_subdiv.h" + +#include "GPU_capabilities.h" +#include "GPU_context.h" + +#include "opensubdiv_capi.h" + +void BKE_subsurf_modifier_subdiv_settings_init(SubdivSettings *settings, + const SubsurfModifierData *smd, + const bool use_render_params) +{ + const int requested_levels = (use_render_params) ? smd->renderLevels : smd->levels; + + settings->is_simple = (smd->subdivType == SUBSURF_TYPE_SIMPLE); + settings->is_adaptive = !(smd->flags & eSubsurfModifierFlag_UseRecursiveSubdivision); + settings->level = settings->is_simple ? + 1 : + (settings->is_adaptive ? smd->quality : requested_levels); + settings->use_creases = (smd->flags & eSubsurfModifierFlag_UseCrease); + settings->vtx_boundary_interpolation = BKE_subdiv_vtx_boundary_interpolation_from_subsurf( + smd->boundary_smooth); + settings->fvar_linear_interpolation = BKE_subdiv_fvar_interpolation_from_uv_smooth( + smd->uv_smooth); +} + +static ModifierData *modifier_get_last_enabled_for_mode(const Scene *scene, + const Object *ob, + int required_mode) +{ + ModifierData *md = ob->modifiers.last; + + while (md) { + if (BKE_modifier_is_enabled(scene, md, required_mode)) { + break; + } + + md = md->prev; + } + + return md; +} + +bool BKE_subsurf_modifier_can_do_gpu_subdiv_ex(const Scene *scene, + const Object *ob, + const SubsurfModifierData *smd, + int required_mode, + bool skip_check_is_last) +{ + if ((U.gpu_flag & USER_GPU_FLAG_SUBDIVISION_EVALUATION) == 0) { + return false; + } + + if (!skip_check_is_last) { + ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode); + if (md != (const ModifierData *)smd) { + return false; + } + } + + /* Only OpenGL is supported for OpenSubdiv evaluation for now. */ + if (GPU_backend_get_type() != GPU_BACKEND_OPENGL) { + return false; + } + + if (!(GPU_compute_shader_support() && GPU_shader_storage_buffer_objects_support())) { + return false; + } + + const int available_evaluators = openSubdiv_getAvailableEvaluators(); + if ((available_evaluators & OPENSUBDIV_EVALUATOR_GLSL_COMPUTE) == 0) { + return false; + } + + return true; +} + +bool BKE_subsurf_modifier_can_do_gpu_subdiv(const Scene *scene, + const Object *ob, + int required_mode) +{ + ModifierData *md = modifier_get_last_enabled_for_mode(scene, ob, required_mode); + + if (!md) { + return false; + } + + if (md->type != eModifierType_Subsurf) { + return false; + } + + return BKE_subsurf_modifier_can_do_gpu_subdiv_ex( + scene, ob, (SubsurfModifierData *)md, required_mode, true); +} + +void (*BKE_subsurf_modifier_free_gpu_cache_cb)(Subdiv *subdiv) = NULL; + +Subdiv *BKE_subsurf_modifier_subdiv_descriptor_ensure(const SubsurfModifierData *smd, + const SubdivSettings *subdiv_settings, + const Mesh *mesh, + const bool for_draw_code) +{ + SubsurfRuntimeData *runtime_data = (SubsurfRuntimeData *)smd->modifier.runtime; + if (runtime_data->subdiv && runtime_data->set_by_draw_code != for_draw_code) { + BKE_subdiv_free(runtime_data->subdiv); + runtime_data->subdiv = NULL; + } + Subdiv *subdiv = BKE_subdiv_update_from_mesh(runtime_data->subdiv, subdiv_settings, mesh); + runtime_data->subdiv = subdiv; + runtime_data->set_by_draw_code = for_draw_code; + return subdiv; +} + +SubsurfRuntimeData *BKE_subsurf_modifier_ensure_runtime(SubsurfModifierData *smd) +{ + SubsurfRuntimeData *runtime_data = (SubsurfRuntimeData *)smd->modifier.runtime; + if (runtime_data == NULL) { + runtime_data = MEM_callocN(sizeof(*runtime_data), "subsurf runtime"); + smd->modifier.runtime = runtime_data; + } + return runtime_data; +} + +int BKE_subsurf_modifier_eval_required_mode(bool is_final_render, bool is_edit_mode) +{ + if (is_final_render) { + return eModifierMode_Render; + } + + return eModifierMode_Realtime | (is_edit_mode ? eModifierMode_Editmode : 0); +} diff --git a/source/blender/blenkernel/intern/subsurf_ccg.c b/source/blender/blenkernel/intern/subsurf_ccg.c index a1b45c2ac7d..9d66c354b54 100644 --- a/source/blender/blenkernel/intern/subsurf_ccg.c +++ b/source/blender/blenkernel/intern/subsurf_ccg.c @@ -717,7 +717,6 @@ static void minmax_v3_v3v3(const float vec[3], float min[3], float max[3]) } } -/* UNUSED, keep since this functionality may be useful in the future. */ static void UNUSED_FUNCTION(ccgDM_getMinMax)(DerivedMesh *dm, float r_min[3], float r_max[3]) { CCGDerivedMesh *ccgdm = (CCGDerivedMesh *)dm; @@ -804,17 +803,11 @@ static int ccgDM_getNumLoops(DerivedMesh *dm) return 4 * ccgSubSurf_getNumFinalFaces(ccgdm->ss); } -static void ccgDM_getFinalVert(DerivedMesh *dm, int vertNum, MVert *mv) +static CCGElem *get_vertex_elem(CCGDerivedMesh *ccgdm, int vertNum) { - CCGDerivedMesh *ccgdm = (CCGDerivedMesh *)dm; CCGSubSurf *ss = ccgdm->ss; - CCGElem *vd; - CCGKey key; int i; - CCG_key_top_level(&key, ss); - memset(mv, 0, sizeof(*mv)); - if ((vertNum < ccgdm->edgeMap[0].startVert) && (ccgSubSurf_getNumFaces(ss) > 0)) { /* this vert comes from face data */ int lastface = ccgSubSurf_getNumFaces(ss) - 1; @@ -843,30 +836,24 @@ static void ccgDM_getFinalVert(DerivedMesh *dm, int vertNum, MVert *mv) offset = vertNum - ccgdm->faceMap[i].startVert; if (offset < 1) { - vd = ccgSubSurf_getFaceCenterData(f); - copy_v3_v3(mv->co, CCG_elem_co(&key, vd)); - normal_float_to_short_v3(mv->no, CCG_elem_no(&key, vd)); + return ccgSubSurf_getFaceCenterData(f); } - else if (offset < gridSideEnd) { + if (offset < gridSideEnd) { offset -= 1; grid = offset / gridSideVerts; x = offset % gridSideVerts + 1; - vd = ccgSubSurf_getFaceGridEdgeData(ss, f, grid, x); - copy_v3_v3(mv->co, CCG_elem_co(&key, vd)); - normal_float_to_short_v3(mv->no, CCG_elem_no(&key, vd)); + return ccgSubSurf_getFaceGridEdgeData(ss, f, grid, x); } - else if (offset < gridInternalEnd) { + if (offset < gridInternalEnd) { offset -= gridSideEnd; grid = offset / gridInternalVerts; offset %= gridInternalVerts; y = offset / gridSideVerts + 1; x = offset % gridSideVerts + 1; - vd = ccgSubSurf_getFaceGridData(ss, f, grid, x, y); - copy_v3_v3(mv->co, CCG_elem_co(&key, vd)); - normal_float_to_short_v3(mv->no, CCG_elem_no(&key, vd)); + return ccgSubSurf_getFaceGridData(ss, f, grid, x, y); } } - else if ((vertNum < ccgdm->vertMap[0].startVert) && (ccgSubSurf_getNumEdges(ss) > 0)) { + if ((vertNum < ccgdm->vertMap[0].startVert) && (ccgSubSurf_getNumEdges(ss) > 0)) { /* this vert comes from edge data */ CCGEdge *e; int lastedge = ccgSubSurf_getNumEdges(ss) - 1; @@ -880,175 +867,39 @@ static void ccgDM_getFinalVert(DerivedMesh *dm, int vertNum, MVert *mv) e = ccgdm->edgeMap[i].edge; x = vertNum - ccgdm->edgeMap[i].startVert + 1; - vd = ccgSubSurf_getEdgeData(ss, e, x); - copy_v3_v3(mv->co, CCG_elem_co(&key, vd)); - normal_float_to_short_v3(mv->no, CCG_elem_no(&key, vd)); + return ccgSubSurf_getEdgeData(ss, e, x); } - else { - /* this vert comes from vert data */ - CCGVert *v; - i = vertNum - ccgdm->vertMap[0].startVert; - v = ccgdm->vertMap[i].vert; - vd = ccgSubSurf_getVertData(ss, v); - copy_v3_v3(mv->co, CCG_elem_co(&key, vd)); - normal_float_to_short_v3(mv->no, CCG_elem_no(&key, vd)); - } -} + /* this vert comes from vert data */ + CCGVert *v; + i = vertNum - ccgdm->vertMap[0].startVert; -static void ccgDM_getFinalVertCo(DerivedMesh *dm, int vertNum, float r_co[3]) -{ - MVert mvert; - - ccgDM_getFinalVert(dm, vertNum, &mvert); - copy_v3_v3(r_co, mvert.co); -} - -static void ccgDM_getFinalVertNo(DerivedMesh *dm, int vertNum, float r_no[3]) -{ - MVert mvert; - - ccgDM_getFinalVert(dm, vertNum, &mvert); - normal_short_to_float_v3(r_no, mvert.no); + v = ccgdm->vertMap[i].vert; + return ccgSubSurf_getVertData(ss, v); } -static void ccgDM_getFinalEdge(DerivedMesh *dm, int edgeNum, MEdge *med) +static void ccgDM_getFinalVertCo(DerivedMesh *dm, int vertNum, float r_co[3]) { 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; - } + CCGElem *vd = get_vertex_elem(ccgdm, vertNum); + CCGKey key; + CCG_key_top_level(&key, ss); + copy_v3_v3(r_co, CCG_elem_co(&key, vd)); } -static void ccgDM_getFinalFace(DerivedMesh *dm, int faceNum, MFace *mf) +static void ccgDM_getFinalVertNo(DerivedMesh *dm, int vertNum, float r_no[3]) { 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; + CCGElem *vd = get_vertex_elem(ccgdm, vertNum); + CCGKey key; + CCG_key_top_level(&key, ss); + copy_v3_v3(r_no, CCG_elem_no(&key, vd)); } -/* 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, const MPoly *mpoly, MVert *mvert, @@ -1090,8 +941,6 @@ void subsurf_copy_grid_hidden(DerivedMesh *dm, } } -/* Translate GridPaintMask into vertex paint masks. Assumes vertices - * are in the order output by ccgDM_copyFinalVertArray. */ void subsurf_copy_grid_paint_mask(DerivedMesh *dm, const MPoly *mpoly, float *paint_mask, @@ -1135,7 +984,6 @@ void subsurf_copy_grid_paint_mask(DerivedMesh *dm, BLI_INLINE void ccgDM_to_MVert(MVert *mv, const CCGKey *key, CCGElem *elem) { copy_v3_v3(mv->co, CCG_elem_co(key, elem)); - normal_float_to_short_v3(mv->no, CCG_elem_no(key, elem)); mv->flag = mv->bweight = 0; } @@ -1920,10 +1768,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 +1876,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 +2003,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. */ diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index 5eb40b6624a..4406647bd2c 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -49,6 +49,7 @@ #include "DNA_text_types.h" #include "DNA_userdef_types.h" +#include "BKE_bpath.h" #include "BKE_idtype.h" #include "BKE_lib_id.h" #include "BKE_main.h" @@ -169,6 +170,15 @@ static void text_free_data(ID *id) #endif } +static void text_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Text *text = (Text *)id; + + if (text->filepath != NULL) { + BKE_bpath_foreach_path_allocated_process(bpath_data, &text->filepath); + } +} + static void text_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Text *text = (Text *)id; @@ -242,6 +252,7 @@ IDTypeInfo IDType_ID_TXT = { .name_plural = "texts", .translation_context = BLT_I18NCONTEXT_ID_TEXT, .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = text_init_data, .copy_data = text_copy_data, @@ -249,6 +260,7 @@ IDTypeInfo IDType_ID_TXT = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = text_foreach_path, .owner_get = NULL, .blend_write = text_blend_write, @@ -267,9 +279,6 @@ IDTypeInfo IDType_ID_TXT = { /** \name Text Add, Free, Validation * \{ */ -/** - * \note caller must handle `compiled` member. - */ void BKE_text_free_lines(Text *text) { for (TextLine *tmp = text->lines.first, *tmp_next; tmp; tmp = tmp_next) { @@ -299,8 +308,6 @@ Text *BKE_text_add(Main *bmain, const char *name) return ta; } -/* this function replaces extended ascii characters */ -/* to a valid utf-8 sequences */ int txt_extended_ascii_as_utf8(char **str) { ptrdiff_t bad_char, i = 0; @@ -463,14 +470,6 @@ bool BKE_text_reload(Text *text) return true; } -/** - * Load a text file. - * - * \param is_internal: If \a true, this text data-block only exists in memory, - * not as a file on disk. - * - * \note text data-blocks have no real user but have 'fake user' enabled by default - */ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const bool is_internal) { unsigned char *buffer; @@ -480,7 +479,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); } @@ -523,11 +522,6 @@ Text *BKE_text_load_ex(Main *bmain, const char *file, const char *relpath, const return ta; } -/** - * Load a text file. - * - * \note Text data-blocks have no user by default, only the 'real user' flag. - */ Text *BKE_text_load(Main *bmain, const char *file, const char *relpath) { return BKE_text_load_ex(bmain, file, relpath, false); @@ -547,11 +541,6 @@ void BKE_text_write(Text *text, const char *str) /* called directly from rna */ txt_make_dirty(text); } -/* returns 0 if file on disk is the same or Text is in memory only - * returns 1 if file has been modified on disk since last local edit - * returns 2 if file on disk has been deleted - * -1 is returned if an error occurs */ - int BKE_text_file_modified_check(Text *text) { BLI_stat_t st; @@ -1131,7 +1120,6 @@ void txt_move_toline(Text *text, unsigned int line, const bool sel) txt_move_to(text, line, 0, sel); } -/* Moves to a certain byte in a line, not a certain utf8-character! */ void txt_move_to(Text *text, unsigned int line, unsigned int ch, const bool sel) { TextLine **linep; @@ -1291,11 +1279,6 @@ void txt_sel_all(Text *text) text->selc = text->sell->len; } -/** - * Reverse of #txt_pop_sel - * Clears the selection and ensures the cursor is located - * at the selection (where the cursor is visually while editing). - */ void txt_sel_clear(Text *text) { if (text->sell) { @@ -1382,9 +1365,6 @@ void txt_sel_set(Text *text, int startl, int startc, int endl, int endc) * - Are not null terminated. * \{ */ -/** - * Create a buffer, the only requirement is #txt_from_buf_for_undo can decode it. - */ char *txt_to_buf_for_undo(Text *text, int *r_buf_len) { int buf_len = 0; @@ -1402,9 +1382,6 @@ char *txt_to_buf_for_undo(Text *text, int *r_buf_len) return buf; } -/** - * Decode a buffer from #txt_to_buf_for_undo. - */ void txt_from_buf_for_undo(Text *text, const char *buf, int buf_len) { const char *buf_end = buf + buf_len; @@ -1977,7 +1954,7 @@ static char tab_to_spaces[] = " "; static void txt_convert_tab_to_spaces(Text *text) { /* sb aims to pad adjust the tab-width needed so that the right number of spaces - * is added so that the indention of the line is the right width (i.e. aligned + * is added so that the indentation of the line is the right width (i.e. aligned * to multiples of TXT_TABSIZE) */ const char *sb = &tab_to_spaces[text->curc % TXT_TABSIZE]; @@ -2401,10 +2378,11 @@ int text_check_bracket(const char ch) return 0; } -/* TODO: have a function for operators - - * http://docs.python.org/py3k/reference/lexical_analysis.html#operators */ bool text_check_delim(const char ch) { + /* TODO: have a function for operators: + * http://docs.python.org/py3k/reference/lexical_analysis.html#operators */ + int a; char delims[] = "():\"\' ~!%^&*-+=[]{};/<>|.#\t,@"; diff --git a/source/blender/blenkernel/intern/texture.c b/source/blender/blenkernel/intern/texture.c index d5f7647f07a..37d5d732a70 100644 --- a/source/blender/blenkernel/intern/texture.c +++ b/source/blender/blenkernel/intern/texture.c @@ -67,6 +67,8 @@ #include "BKE_scene.h" #include "BKE_texture.h" +#include "NOD_texture.h" + #include "RE_texture.h" #include "BLO_read_write.h" @@ -142,9 +144,10 @@ 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) @@ -184,7 +187,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,6 +213,7 @@ IDTypeInfo IDType_ID_TE = { .name_plural = "textures", .translation_context = BLT_I18NCONTEXT_ID_TEXTURE, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = texture_init_data, .copy_data = texture_copy_data, @@ -218,6 +221,7 @@ IDTypeInfo IDType_ID_TE = { .make_local = NULL, .foreach_id = texture_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = texture_blend_write, @@ -230,11 +234,10 @@ IDTypeInfo IDType_ID_TE = { .lib_override_apply_post = NULL, }; -/* 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 ******************* */ @@ -412,7 +415,6 @@ MTex *BKE_texture_mtex_add(void) return mtex; } -/* slot -1 for first free ID */ MTex *BKE_texture_mtex_add_id(ID *id, int slot) { MTex **mtex_ar; @@ -670,9 +672,6 @@ void BKE_texture_pointdensity_free(PointDensity *pd) } /* ------------------------------------------------------------------------- */ -/** - * \returns true if this texture can use its #Texture.ima (even if its NULL) - */ bool BKE_texture_is_image_user(const struct Tex *tex) { switch (tex->type) { @@ -684,7 +683,6 @@ bool BKE_texture_is_image_user(const struct Tex *tex) return false; } -/* ------------------------------------------------------------------------- */ bool BKE_texture_dependsOnTime(const struct Tex *texture) { if (texture->ima && BKE_image_is_animated(texture->ima)) { @@ -758,7 +756,6 @@ static void texture_nodes_fetch_images_for_pool(Tex *texture, } } -/* Make sure all images used by texture are loaded into pool. */ void BKE_texture_fetch_images_for_pool(Tex *texture, struct ImagePool *pool) { if (texture->nodetree != NULL) { diff --git a/source/blender/blenkernel/intern/tracking.c b/source/blender/blenkernel/intern/tracking.c index 068d048fd08..3878d3b1c98 100644 --- a/source/blender/blenkernel/intern/tracking.c +++ b/source/blender/blenkernel/intern/tracking.c @@ -160,11 +160,6 @@ static void tracking_dopesheet_free(MovieTrackingDopesheet *dopesheet) dopesheet->tot_channel = 0; } -/* Free tracking structure, only frees structure contents - * (if structure is allocated in heap, it shall be handled outside). - * - * All the pointers inside structure becomes invalid after this call. - */ void BKE_tracking_free(MovieTracking *tracking) { tracking_tracks_free(&tracking->tracks); @@ -276,7 +271,6 @@ static void tracking_objects_copy(ListBase *objects_dst, } } -/* Copy tracking structure content. */ void BKE_tracking_copy(MovieTracking *tracking_dst, const MovieTracking *tracking_src, const int flag) @@ -321,9 +315,6 @@ void BKE_tracking_copy(MovieTracking *tracking_dst, BLI_ghash_free(tracks_mapping, NULL, NULL); } -/* Initialize motion tracking settings to default values, - * used when new movie clip datablock is created. - */ void BKE_tracking_settings_init(MovieTracking *tracking) { tracking->camera.sensor_width = 35.0f; @@ -361,7 +352,6 @@ void BKE_tracking_settings_init(MovieTracking *tracking) BKE_tracking_object_add(tracking, "Camera"); } -/* Get list base of active object's tracks. */ ListBase *BKE_tracking_get_active_tracks(MovieTracking *tracking) { MovieTrackingObject *object = BKE_tracking_object_get_active(tracking); @@ -373,7 +363,6 @@ ListBase *BKE_tracking_get_active_tracks(MovieTracking *tracking) return &tracking->tracks; } -/* Get list base of active object's plane tracks. */ ListBase *BKE_tracking_get_active_plane_tracks(MovieTracking *tracking) { MovieTrackingObject *object = BKE_tracking_object_get_active(tracking); @@ -385,7 +374,6 @@ ListBase *BKE_tracking_get_active_plane_tracks(MovieTracking *tracking) return &tracking->plane_tracks; } -/* Get reconstruction data of active object. */ MovieTrackingReconstruction *BKE_tracking_get_active_reconstruction(MovieTracking *tracking) { MovieTrackingObject *object = BKE_tracking_object_get_active(tracking); @@ -393,9 +381,6 @@ MovieTrackingReconstruction *BKE_tracking_get_active_reconstruction(MovieTrackin return BKE_tracking_object_get_reconstruction(tracking, object); } -/* Get transformation matrix for a given object which is used - * for parenting motion tracker reconstruction to 3D world. - */ void BKE_tracking_get_camera_object_matrix(Object *camera_object, float mat[4][4]) { BLI_assert(camera_object != NULL); @@ -412,11 +397,6 @@ void BKE_tracking_get_camera_object_matrix(Object *camera_object, float mat[4][4 BKE_object_where_is_calc_mat4(camera_object, mat); } -/* Get projection matrix for camera specified by given tracking object - * and frame number. - * - * NOTE: frame number should be in clip space, not scene space - */ void BKE_tracking_get_projection_matrix(MovieTracking *tracking, MovieTrackingObject *object, int framenr, @@ -472,7 +452,6 @@ void BKE_tracking_get_projection_matrix(MovieTracking *tracking, /*********************** clipboard *************************/ -/* Free clipboard by freeing memory used by all tracks in it. */ void BKE_tracking_clipboard_free(void) { MovieTrackingTrack *track = tracking_clipboard.tracks.first, *next_track; @@ -489,7 +468,6 @@ void BKE_tracking_clipboard_free(void) BLI_listbase_clear(&tracking_clipboard.tracks); } -/* Copy selected tracks from specified object to the clipboard. */ void BKE_tracking_clipboard_copy_tracks(MovieTracking *tracking, MovieTrackingObject *object) { ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object); @@ -510,17 +488,11 @@ void BKE_tracking_clipboard_copy_tracks(MovieTracking *tracking, MovieTrackingOb } } -/* Check whether there are any tracks in the clipboard. */ bool BKE_tracking_clipboard_has_tracks(void) { return (BLI_listbase_is_empty(&tracking_clipboard.tracks) == false); } -/* Paste tracks from clipboard to specified object. - * - * Names of new tracks in object are guaranteed to - * be unique here. - */ void BKE_tracking_clipboard_paste_tracks(MovieTracking *tracking, MovieTrackingObject *object) { ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object); @@ -541,10 +513,6 @@ void BKE_tracking_clipboard_paste_tracks(MovieTracking *tracking, MovieTrackingO /*********************** Tracks *************************/ -/* Add new empty track to the given list of tracks. - * - * It is required that caller will append at least one marker to avoid degenerate tracks. - */ MovieTrackingTrack *BKE_tracking_track_add_empty(MovieTracking *tracking, ListBase *tracks_list) { const MovieTrackingSettings *settings = &tracking->settings; @@ -569,14 +537,6 @@ MovieTrackingTrack *BKE_tracking_track_add_empty(MovieTracking *tracking, ListBa return track; } -/* Add new track to a specified tracks base. - * - * Coordinates are expected to be in normalized 0..1 space, - * frame number is expected to be in clip space. - * - * Width and height are clip's dimension used to scale track's - * pattern and search regions. - */ MovieTrackingTrack *BKE_tracking_track_add(MovieTracking *tracking, ListBase *tracksbase, float x, @@ -618,7 +578,6 @@ MovieTrackingTrack *BKE_tracking_track_add(MovieTracking *tracking, return track; } -/* Duplicate the specified track, result will no belong to any list. */ MovieTrackingTrack *BKE_tracking_track_duplicate(MovieTrackingTrack *track) { MovieTrackingTrack *new_track; @@ -639,10 +598,6 @@ MovieTrackingTrack *BKE_tracking_track_duplicate(MovieTrackingTrack *track) return new_track; } -/* Ensure specified track has got unique name, - * if it's not name of specified track will be changed - * keeping names of all other tracks unchanged. - */ void BKE_tracking_track_unique_name(ListBase *tracksbase, MovieTrackingTrack *track) { BLI_uniquename(tracksbase, @@ -653,11 +608,6 @@ void BKE_tracking_track_unique_name(ListBase *tracksbase, MovieTrackingTrack *tr sizeof(track->name)); } -/* Free specified track, only frees contents of a structure - * (if track is allocated in heap, it shall be handled outside). - * - * All the pointers inside track becomes invalid after this call. - */ void BKE_tracking_track_free(MovieTrackingTrack *track) { if (track->markers) { @@ -665,8 +615,6 @@ void BKE_tracking_track_free(MovieTrackingTrack *track) } } -/* Get frame numbers of the very first and last markers. - * There is no check on whether the marker is enabled or not. */ void BKE_tracking_track_first_last_frame_get(const MovieTrackingTrack *track, int *r_first_frame, int *r_last_frame) @@ -677,9 +625,6 @@ void BKE_tracking_track_first_last_frame_get(const MovieTrackingTrack *track, *r_last_frame = track->markers[last_marker_index].framenr; } -/* Find the minimum starting frame and maximum ending frame within given set of - * tracks. - */ void BKE_tracking_tracks_first_last_frame_minmax(/*const*/ MovieTrackingTrack **tracks, const int num_tracks, int *r_first_frame, @@ -745,11 +690,6 @@ MovieTrackingTrack **BKE_tracking_selected_tracks_in_active_object(MovieTracking return source_tracks; } -/* Set flag for all specified track's areas. - * - * area - which part of marker should be selected. see TRACK_AREA_* constants. - * flag - flag to be set for areas. - */ void BKE_tracking_track_flag_set(MovieTrackingTrack *track, int area, int flag) { if (area == TRACK_AREA_NONE) { @@ -767,11 +707,6 @@ void BKE_tracking_track_flag_set(MovieTrackingTrack *track, int area, int flag) } } -/* Clear flag from all specified track's areas. - * - * area - which part of marker should be selected. see TRACK_AREA_* constants. - * flag - flag to be cleared for areas. - */ void BKE_tracking_track_flag_clear(MovieTrackingTrack *track, int area, int flag) { if (area == TRACK_AREA_NONE) { @@ -789,19 +724,11 @@ void BKE_tracking_track_flag_clear(MovieTrackingTrack *track, int area, int flag } } -/* Check whether track has got marker at specified frame. - * - * NOTE: frame number should be in clip space, not scene space. - */ bool BKE_tracking_track_has_marker_at_frame(MovieTrackingTrack *track, int framenr) { return BKE_tracking_marker_get_exact(track, framenr) != NULL; } -/* Check whether track has got enabled marker at specified frame. - * - * NOTE: frame number should be in clip space, not scene space. - */ bool BKE_tracking_track_has_enabled_marker_at_frame(MovieTrackingTrack *track, int framenr) { MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr); @@ -809,18 +736,6 @@ bool BKE_tracking_track_has_enabled_marker_at_frame(MovieTrackingTrack *track, i return marker && (marker->flag & MARKER_DISABLED) == 0; } -/* Clear track's path: - * - * - If action is TRACK_CLEAR_REMAINED path from ref_frame+1 up to - * end will be clear. - * - * - If action is TRACK_CLEAR_UPTO path from the beginning up to - * ref_frame-1 will be clear. - * - * - If action is TRACK_CLEAR_ALL only marker at frame ref_frame will remain. - * - * NOTE: frame number should be in clip space, not scene space - */ void BKE_tracking_track_path_clear(MovieTrackingTrack *track, int ref_frame, int action) { int a; @@ -1281,7 +1196,6 @@ static void track_mask_gpencil_layer_rasterize(int frame_width, } } -/* Region is in pixel space, relative to marker's center. */ float *tracking_track_get_mask_for_region(int frame_width, int frame_height, const float region_min[2], @@ -1336,7 +1250,6 @@ float BKE_tracking_track_get_weight_for_marker(MovieClip *clip, return weight; } -/* area - which part of marker should be selected. see TRACK_AREA_* constants */ void BKE_tracking_track_select(ListBase *tracksbase, MovieTrackingTrack *track, int area, @@ -1510,16 +1423,6 @@ void BKE_tracking_marker_clamp(MovieTrackingMarker *marker, int event) } } -/** - * Get marker closest to the given frame number. - * - * If there is maker with exact frame number it returned. - * Otherwise, marker with highest frame number but lower than the requested - * frame is returned if such marker exists. Otherwise, the marker with lowest - * frame number greater than the requested frame number is returned. - * - * This function has complexity of `O(log number_of_markers)`. - */ MovieTrackingMarker *BKE_tracking_marker_get(MovieTrackingTrack *track, int framenr) { const int num_markers = track->markersnr; @@ -1705,7 +1608,6 @@ void BKE_tracking_marker_get_subframe_position(MovieTrackingTrack *track, /*********************** Plane Track *************************/ -/* Creates new plane track out of selected point tracks */ MovieTrackingPlaneTrack *BKE_tracking_plane_track_add(MovieTracking *tracking, ListBase *plane_tracks_base, ListBase *tracks, @@ -1789,11 +1691,6 @@ void BKE_tracking_plane_track_unique_name(ListBase *plane_tracks_base, sizeof(plane_track->name)); } -/* Free specified plane track, only frees contents of a structure - * (if track is allocated in heap, it shall be handled outside). - * - * All the pointers inside track becomes invalid after this call. - */ void BKE_tracking_plane_track_free(MovieTrackingPlaneTrack *plane_track) { if (plane_track->markers) { @@ -1988,9 +1885,6 @@ void BKE_tracking_plane_marker_delete(MovieTrackingPlaneTrack *plane_track, int * would be nice to de-duplicate them somehow.. */ -/* Get a plane marker at given frame, - * If there's no such marker, closest one from the left side will be returned. - */ MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get(MovieTrackingPlaneTrack *plane_track, int framenr) { @@ -2039,9 +1933,6 @@ MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get(MovieTrackingPlaneTrack return NULL; } -/* Get a plane marker at exact given frame, if there's no marker at the frame, - * NULL will be returned. - */ MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get_exact(MovieTrackingPlaneTrack *plane_track, int framenr) { @@ -2054,7 +1945,6 @@ MovieTrackingPlaneMarker *BKE_tracking_plane_marker_get_exact(MovieTrackingPlane return plane_marker; } -/* Ensure there's a marker for the given frame. */ MovieTrackingPlaneMarker *BKE_tracking_plane_marker_ensure(MovieTrackingPlaneTrack *plane_track, int framenr) { @@ -2326,7 +2216,6 @@ static void reconstructed_camera_scale_set(MovieTrackingObject *object, float ma } } -/* converts principal offset from center to offset of blender's camera */ void BKE_tracking_camera_shift_get( MovieTracking *tracking, int winx, int winy, float *shiftx, float *shifty) { @@ -2899,10 +2788,6 @@ ImBuf *BKE_tracking_get_search_imbuf(ImBuf *ibuf, return searchibuf; } -/* zap channels from the imbuf that are disabled by the user. this can lead to - * better tracks sometimes. however, instead of simply zeroing the channels - * out, do a partial grayscale conversion so the display is better. - */ void BKE_tracking_disable_channels( ImBuf *ibuf, bool disable_red, bool disable_green, bool disable_blue, bool grayscale) { @@ -3014,6 +2899,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 +2993,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 +3055,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 +3178,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 +3198,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); + } } } @@ -3315,9 +3302,6 @@ static void tracking_dopesheet_calc_coverage(MovieTracking *tracking) MEM_freeN(per_frame_counter); } -/* Tag dopesheet for update, actual update will happen later - * when it'll be actually needed. - */ void BKE_tracking_dopesheet_tag_update(MovieTracking *tracking) { MovieTrackingDopesheet *dopesheet = &tracking->dopesheet; @@ -3325,7 +3309,6 @@ void BKE_tracking_dopesheet_tag_update(MovieTracking *tracking) dopesheet->ok = false; } -/* Do dopesheet update, if update is not needed nothing will happen. */ void BKE_tracking_dopesheet_update(MovieTracking *tracking) { MovieTrackingDopesheet *dopesheet = &tracking->dopesheet; @@ -3349,7 +3332,6 @@ void BKE_tracking_dopesheet_update(MovieTracking *tracking) dopesheet->ok = true; } -/* NOTE: Returns NULL if the track comes from camera object, */ MovieTrackingObject *BKE_tracking_find_object_for_track(const MovieTracking *tracking, const MovieTrackingTrack *track) { @@ -3377,7 +3359,6 @@ ListBase *BKE_tracking_find_tracks_list_for_track(MovieTracking *tracking, return &tracking->tracks; } -/* NOTE: Returns NULL if the track comes from camera object, */ MovieTrackingObject *BKE_tracking_find_object_for_plane_track( const MovieTracking *tracking, const MovieTrackingPlaneTrack *plane_track) { diff --git a/source/blender/blenkernel/intern/tracking_auto.c b/source/blender/blenkernel/intern/tracking_auto.c index 92ff0911cf3..c83e595c611 100644 --- a/source/blender/blenkernel/intern/tracking_auto.c +++ b/source/blender/blenkernel/intern/tracking_auto.c @@ -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; @@ -775,7 +775,7 @@ void BKE_autotrack_context_sync(AutoTrackContext *context) } /* TODO(sergey): Find a way to avoid this, somehow making all needed logic in - * BKE_autotrack_context_sync(). */ + * #BKE_autotrack_context_sync(). */ void BKE_autotrack_context_sync_user(AutoTrackContext *context, MovieClipUser *user) { user->framenr = context->synchronized_scene_frame; diff --git a/source/blender/blenkernel/intern/tracking_detect.c b/source/blender/blenkernel/intern/tracking_detect.c index 08719161e1a..be680ef8a8b 100644 --- a/source/blender/blenkernel/intern/tracking_detect.c +++ b/source/blender/blenkernel/intern/tracking_detect.c @@ -148,7 +148,6 @@ static void run_configured_detector(MovieTracking *tracking, } } -/* Detect features using FAST detector */ void BKE_tracking_detect_fast(MovieTracking *tracking, ListBase *tracksbase, ImBuf *ibuf, @@ -170,7 +169,6 @@ void BKE_tracking_detect_fast(MovieTracking *tracking, tracking, tracksbase, ibuf, framenr, layer, place_outside_layer, &options); } -/* Detect features using Harris detector */ void BKE_tracking_detect_harris(MovieTracking *tracking, ListBase *tracksbase, ImBuf *ibuf, diff --git a/source/blender/blenkernel/intern/tracking_plane_tracker.c b/source/blender/blenkernel/intern/tracking_plane_tracker.c index b787cd366c5..d4a5bb2aa9d 100644 --- a/source/blender/blenkernel/intern/tracking_plane_tracker.c +++ b/source/blender/blenkernel/intern/tracking_plane_tracker.c @@ -167,7 +167,6 @@ static void track_plane_from_existing_motion(MovieTrackingPlaneTrack *plane_trac } } -/* NOTE: frame number should be in clip space, not scene space */ void BKE_tracking_track_plane_from_existing_motion(MovieTrackingPlaneTrack *plane_track, int start_frame) { diff --git a/source/blender/blenkernel/intern/tracking_region_tracker.c b/source/blender/blenkernel/intern/tracking_region_tracker.c index 179def0a6f2..4b23f74bc8f 100644 --- a/source/blender/blenkernel/intern/tracking_region_tracker.c +++ b/source/blender/blenkernel/intern/tracking_region_tracker.c @@ -26,6 +26,7 @@ #include "MEM_guardedalloc.h" +#include "DNA_defaults.h" #include "DNA_movieclip_types.h" #include "BLI_threads.h" @@ -42,7 +43,7 @@ /* **** utility functions for tracking **** */ -/* convert from float and byte RGBA to grayscale. Supports different coefficients for RGB. */ +/** Convert from float and byte RGBA to gray-scale. Supports different coefficients for RGB. */ static void float_rgba_to_gray(const float *rgba, float *gray, int num_pixels, @@ -71,7 +72,7 @@ static void uint8_rgba_to_float_gray(const unsigned char *rgba, } } -/* Get grayscale float search buffer for given marker and frame. */ +/** Get gray-scale float search buffer for given marker and frame. */ static float *track_get_search_floatbuf(ImBuf *ibuf, MovieTrackingTrack *track, MovieTrackingMarker *marker, @@ -180,7 +181,6 @@ static ImBuf *tracking_context_get_reference_ibuf(MovieClip *clip, return ibuf; } -/* Fill in libmv tracker options structure with settings need to be used to perform track. */ void tracking_configure_tracker(const MovieTrackingTrack *track, float *mask, const bool is_backwards, @@ -311,11 +311,6 @@ static bool refine_marker_reference_frame_get(MovieTrackingTrack *track, return (reference->flag & MARKER_DISABLED) == 0; } -/* Refine marker's position using previously known keyframe. - * Direction of searching for a keyframe depends on backwards flag, - * which means if backwards is false, previous keyframe will be as - * reference. - */ void BKE_tracking_refine_marker(MovieClip *clip, MovieTrackingTrack *track, MovieTrackingMarker *marker, @@ -328,7 +323,7 @@ void BKE_tracking_refine_marker(MovieClip *clip, int search_area_height, search_area_width; int clip_flag = clip->flag & MCLIP_TIMECODE_FLAGS; int reference_framenr; - MovieClipUser user = {0}; + MovieClipUser user = *DNA_struct_default_get(MovieClipUser); double dst_pixel_x[5], dst_pixel_y[5]; bool tracked; diff --git a/source/blender/blenkernel/intern/tracking_solver.c b/source/blender/blenkernel/intern/tracking_solver.c index d89d36f85ea..47a955c9d93 100644 --- a/source/blender/blenkernel/intern/tracking_solver.c +++ b/source/blender/blenkernel/intern/tracking_solver.c @@ -325,7 +325,6 @@ static int reconstruct_count_tracks_on_both_keyframes(MovieTracking *tracking, return tot; } -/* Perform early check on whether everything is fine to start reconstruction. */ bool BKE_tracking_reconstruction_check(MovieTracking *tracking, MovieTrackingObject *object, char *error_msg, @@ -354,11 +353,6 @@ bool BKE_tracking_reconstruction_check(MovieTracking *tracking, return true; } -/* Create context for camera/object motion reconstruction. - * Copies all data needed for reconstruction from movie - * clip datablock, so editing this clip is safe during - * reconstruction job is in progress. - */ MovieReconstructContext *BKE_tracking_reconstruction_context_new(MovieClip *clip, MovieTrackingObject *object, int keyframe1, @@ -446,7 +440,6 @@ const char *BKE_tracking_reconstruction_error_message_get(const MovieReconstruct return context->error_message; } -/* Free memory used by a reconstruction process. */ void BKE_tracking_reconstruction_context_free(MovieReconstructContext *context) { if (context->reconstruction) { @@ -486,15 +479,6 @@ static void reconstructionOptionsFromContext(libmv_ReconstructionOptions *recons reconstruction_options->refine_intrinsics = context->refine_flags; } -/* Solve camera/object motion and reconstruct 3D markers position - * from a prepared reconstruction context. - * - * stop is not actually used at this moment, so reconstruction - * job could not be stopped. - * - * do_update, progress and stat_message are set by reconstruction - * callback in libmv side and passing to an interface. - */ void BKE_tracking_reconstruction_solve(MovieReconstructContext *context, short *stop, short *do_update, @@ -542,9 +526,6 @@ void BKE_tracking_reconstruction_solve(MovieReconstructContext *context, context->reprojection_error = error; } -/* Finish reconstruction process by copying reconstructed data - * to an actual movie clip datablock. - */ bool BKE_tracking_reconstruction_finish(MovieReconstructContext *context, MovieTracking *tracking) { MovieTrackingReconstruction *reconstruction; @@ -608,9 +589,6 @@ static void tracking_scale_reconstruction(ListBase *tracksbase, } } -/* Apply scale on all reconstructed cameras and bundles, - * used by camera scale apply operator. - */ void BKE_tracking_reconstruction_scale(MovieTracking *tracking, float scale[3]) { LISTBASE_FOREACH (MovieTrackingObject *, object, &tracking->objects) { diff --git a/source/blender/blenkernel/intern/tracking_stabilize.c b/source/blender/blenkernel/intern/tracking_stabilize.c index d5585116f7e..fe9a2f2268a 100644 --- a/source/blender/blenkernel/intern/tracking_stabilize.c +++ b/source/blender/blenkernel/intern/tracking_stabilize.c @@ -1252,24 +1252,6 @@ static StabContext *init_stabilizer(MovieClip *clip, int size, float aspect) /* === public interface functions === */ -/* Get stabilization data (translation, scaling and angle) for a given frame. - * Returned data describes how to compensate the detected movement, but with any - * chosen scale factor already applied and any target frame position already - * compensated. In case stabilization fails or is disabled, neutral values are - * returned. - * - * framenr is a frame number, relative to the clip (not relative to the scene - * timeline) - * width is an effective width of the canvas (square pixels), used to scale the - * determined translation - * - * Outputs: - * - translation of the lateral shift, absolute canvas coordinates - * (square pixels). - * - scale of the scaling to apply - * - angle of the rotation angle, relative to the frame center - */ -/* TODO(sergey): Use r_ prefix for output parameters here. */ void BKE_tracking_stabilization_data_get(MovieClip *clip, int framenr, int width, @@ -1307,7 +1289,7 @@ void BKE_tracking_stabilization_data_get(MovieClip *clip, discard_stabilization_working_context(ctx); } -typedef void (*interpolation_func)(struct ImBuf *, struct ImBuf *, float, float, int, int); +typedef void (*interpolation_func)(const struct ImBuf *, struct ImBuf *, float, float, int, int); typedef struct TrackingStabilizeFrameInterpolationData { ImBuf *ibuf; @@ -1336,12 +1318,6 @@ static void tracking_stabilize_frame_interpolation_cb( } } -/* Stabilize given image buffer using stabilization data for a specified - * frame number. - * - * NOTE: frame number should be in clip space, not scene space. - */ -/* TODO(sergey): Use r_ prefix for output parameters here. */ ImBuf *BKE_tracking_stabilize_frame( MovieClip *clip, int framenr, ImBuf *ibuf, float translation[2], float *scale, float *angle) { @@ -1449,16 +1425,6 @@ ImBuf *BKE_tracking_stabilize_frame( return tmpibuf; } -/* Build a 4x4 transformation matrix based on the given 2D stabilization data. - * mat is a 4x4 matrix in homogeneous coordinates, adapted to the - * final image buffer size and compensated for pixel aspect ratio, - * ready for direct OpenGL drawing. - * - * TODO(sergey): The signature of this function should be changed. we actually - * don't need the dimensions of the image buffer. Instead we - * should consider to provide the pivot point of the rotation as a - * further stabilization data parameter. - */ void BKE_tracking_stabilization_data_to_mat4(int buffer_width, int buffer_height, float pixel_aspect, diff --git a/source/blender/blenkernel/intern/tracking_test.cc b/source/blender/blenkernel/intern/tracking_test.cc index a3845dcad8f..d85d71b7c86 100644 --- a/source/blender/blenkernel/intern/tracking_test.cc +++ b/source/blender/blenkernel/intern/tracking_test.cc @@ -5,7 +5,7 @@ #include "DNA_tracking_types.h" #include "BKE_tracking.h" -#include "BLI_float2.hh" +#include "BLI_math_vec_types.hh" namespace blender { diff --git a/source/blender/blenkernel/intern/tracking_util.c b/source/blender/blenkernel/intern/tracking_util.c index 16b36e94328..f1fd3a13e0e 100644 --- a/source/blender/blenkernel/intern/tracking_util.c +++ b/source/blender/blenkernel/intern/tracking_util.c @@ -338,11 +338,6 @@ static void search_pixel_to_marker_unified(int frame_width, sub_v2_v2v2(marker_unified, frame_unified, marker->pos); } -/* Each marker has 5 coordinates associated with it that get warped with - * tracking: the four corners ("pattern_corners"), and the center ("pos"). - * This function puts those 5 points into the appropriate frame for tracking - * (the "search" coordinate frame). - */ void tracking_get_marker_coords_for_tracking(int frame_width, int frame_height, const MovieTrackingMarker *marker, @@ -369,7 +364,6 @@ void tracking_get_marker_coords_for_tracking(int frame_width, search_pixel_y[4] = pixel_coords[1] - 0.5f; } -/* Inverse of above. */ void tracking_set_marker_coords_from_tracking(int frame_width, int frame_height, MovieTrackingMarker *marker, @@ -411,15 +405,6 @@ void tracking_set_marker_coords_from_tracking(int frame_width, /** \name General Purpose Utility Functions * \{ */ -/* Place a disabled marker before or after specified ref_marker. - * - * If before is truth, disabled marker is placed before reference - * one, and it's placed after it otherwise. - * - * If there's already a marker at the frame where disabled one - * is expected to be placed, nothing will happen if overwrite - * is false. - */ void tracking_marker_insert_disabled(MovieTrackingTrack *track, const MovieTrackingMarker *ref_marker, bool before, @@ -526,7 +511,6 @@ static void distortion_model_parameters_from_options( BLI_assert_msg(0, "Unknown distortion model"); } -/* Fill in Libmv C-API camera intrinsics options from tracking structure. */ void tracking_cameraIntrinscisOptionsFromTracking( MovieTracking *tracking, int calibration_width, @@ -563,7 +547,6 @@ void tracking_trackingCameraFromIntrinscisOptions( distortion_model_parameters_from_options(camera_intrinsics_options, camera); } -/* Get previous keyframed marker. */ MovieTrackingMarker *tracking_get_keyframed_marker(MovieTrackingTrack *track, int current_frame, bool backwards) diff --git a/source/blender/blenkernel/intern/type_conversions.cc b/source/blender/blenkernel/intern/type_conversions.cc new file mode 100644 index 00000000000..cb05337ef2a --- /dev/null +++ b/source/blender/blenkernel/intern/type_conversions.cc @@ -0,0 +1,363 @@ +/* + * 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_type_conversions.hh" + +#include "FN_multi_function_builder.hh" + +#include "BLI_color.hh" +#include "BLI_math_vec_types.hh" + +namespace blender::bke { + +using fn::MFDataType; + +template<typename From, typename To, To (*ConversionF)(const From &)> +static void add_implicit_conversion(DataTypeConversions &conversions) +{ + static const CPPType &from_type = CPPType::get<From>(); + static const CPPType &to_type = CPPType::get<To>(); + static const std::string conversion_name = from_type.name() + " to " + to_type.name(); + + static fn::CustomMF_SI_SO<From, To> multi_function{conversion_name.c_str(), ConversionF}; + static auto convert_single_to_initialized = [](const void *src, void *dst) { + *(To *)dst = ConversionF(*(const From *)src); + }; + static auto convert_single_to_uninitialized = [](const void *src, void *dst) { + new (dst) To(ConversionF(*(const From *)src)); + }; + conversions.add(fn::MFDataType::ForSingle<From>(), + fn::MFDataType::ForSingle<To>(), + multi_function, + convert_single_to_initialized, + convert_single_to_uninitialized); +} + +static float2 float_to_float2(const float &a) +{ + return float2(a); +} +static float3 float_to_float3(const float &a) +{ + return float3(a); +} +static int32_t float_to_int(const float &a) +{ + return (int32_t)a; +} +static bool float_to_bool(const float &a) +{ + return a > 0.0f; +} +static ColorGeometry4f float_to_color(const float &a) +{ + return ColorGeometry4f(a, a, a, 1.0f); +} + +static float3 float2_to_float3(const float2 &a) +{ + return float3(a.x, a.y, 0.0f); +} +static float float2_to_float(const float2 &a) +{ + return (a.x + a.y) / 2.0f; +} +static int float2_to_int(const float2 &a) +{ + return (int32_t)((a.x + a.y) / 2.0f); +} +static bool float2_to_bool(const float2 &a) +{ + return !is_zero_v2(a); +} +static ColorGeometry4f float2_to_color(const float2 &a) +{ + return ColorGeometry4f(a.x, a.y, 0.0f, 1.0f); +} + +static bool float3_to_bool(const float3 &a) +{ + return !is_zero_v3(a); +} +static float float3_to_float(const float3 &a) +{ + return (a.x + a.y + a.z) / 3.0f; +} +static int float3_to_int(const float3 &a) +{ + return (int)((a.x + a.y + a.z) / 3.0f); +} +static float2 float3_to_float2(const float3 &a) +{ + return float2(a); +} +static ColorGeometry4f float3_to_color(const float3 &a) +{ + return ColorGeometry4f(a.x, a.y, a.z, 1.0f); +} + +static bool int_to_bool(const int32_t &a) +{ + return a > 0; +} +static float int_to_float(const int32_t &a) +{ + return (float)a; +} +static float2 int_to_float2(const int32_t &a) +{ + return float2((float)a); +} +static float3 int_to_float3(const int32_t &a) +{ + return float3((float)a); +} +static ColorGeometry4f int_to_color(const int32_t &a) +{ + return ColorGeometry4f((float)a, (float)a, (float)a, 1.0f); +} + +static float bool_to_float(const bool &a) +{ + return (bool)a; +} +static int32_t bool_to_int(const bool &a) +{ + return (int32_t)a; +} +static float2 bool_to_float2(const bool &a) +{ + return (a) ? float2(1.0f) : float2(0.0f); +} +static float3 bool_to_float3(const bool &a) +{ + return (a) ? float3(1.0f) : float3(0.0f); +} +static ColorGeometry4f bool_to_color(const bool &a) +{ + return (a) ? ColorGeometry4f(1.0f, 1.0f, 1.0f, 1.0f) : ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f); +} + +static bool color_to_bool(const ColorGeometry4f &a) +{ + return rgb_to_grayscale(a) > 0.0f; +} +static float color_to_float(const ColorGeometry4f &a) +{ + return rgb_to_grayscale(a); +} +static int32_t color_to_int(const ColorGeometry4f &a) +{ + return (int)rgb_to_grayscale(a); +} +static float2 color_to_float2(const ColorGeometry4f &a) +{ + return float2(a.r, a.g); +} +static float3 color_to_float3(const ColorGeometry4f &a) +{ + return float3(a.r, a.g, a.b); +} + +static DataTypeConversions create_implicit_conversions() +{ + DataTypeConversions conversions; + + add_implicit_conversion<float, float2, float_to_float2>(conversions); + add_implicit_conversion<float, float3, float_to_float3>(conversions); + add_implicit_conversion<float, int32_t, float_to_int>(conversions); + add_implicit_conversion<float, bool, float_to_bool>(conversions); + add_implicit_conversion<float, ColorGeometry4f, float_to_color>(conversions); + + add_implicit_conversion<float2, float3, float2_to_float3>(conversions); + add_implicit_conversion<float2, float, float2_to_float>(conversions); + add_implicit_conversion<float2, int32_t, float2_to_int>(conversions); + add_implicit_conversion<float2, bool, float2_to_bool>(conversions); + add_implicit_conversion<float2, ColorGeometry4f, float2_to_color>(conversions); + + add_implicit_conversion<float3, bool, float3_to_bool>(conversions); + add_implicit_conversion<float3, float, float3_to_float>(conversions); + add_implicit_conversion<float3, int32_t, float3_to_int>(conversions); + add_implicit_conversion<float3, float2, float3_to_float2>(conversions); + add_implicit_conversion<float3, ColorGeometry4f, float3_to_color>(conversions); + + add_implicit_conversion<int32_t, bool, int_to_bool>(conversions); + add_implicit_conversion<int32_t, float, int_to_float>(conversions); + add_implicit_conversion<int32_t, float2, int_to_float2>(conversions); + add_implicit_conversion<int32_t, float3, int_to_float3>(conversions); + add_implicit_conversion<int32_t, ColorGeometry4f, int_to_color>(conversions); + + add_implicit_conversion<bool, float, bool_to_float>(conversions); + add_implicit_conversion<bool, int32_t, bool_to_int>(conversions); + add_implicit_conversion<bool, float2, bool_to_float2>(conversions); + add_implicit_conversion<bool, float3, bool_to_float3>(conversions); + add_implicit_conversion<bool, ColorGeometry4f, bool_to_color>(conversions); + + add_implicit_conversion<ColorGeometry4f, bool, color_to_bool>(conversions); + add_implicit_conversion<ColorGeometry4f, float, color_to_float>(conversions); + add_implicit_conversion<ColorGeometry4f, int32_t, color_to_int>(conversions); + add_implicit_conversion<ColorGeometry4f, float2, color_to_float2>(conversions); + add_implicit_conversion<ColorGeometry4f, float3, color_to_float3>(conversions); + + return conversions; +} + +const DataTypeConversions &get_implicit_type_conversions() +{ + static const DataTypeConversions conversions = create_implicit_conversions(); + return conversions; +} + +void DataTypeConversions::convert_to_uninitialized(const CPPType &from_type, + const CPPType &to_type, + const void *from_value, + void *to_value) const +{ + if (from_type == to_type) { + from_type.copy_construct(from_value, to_value); + return; + } + + const ConversionFunctions *functions = this->get_conversion_functions( + MFDataType::ForSingle(from_type), MFDataType::ForSingle(to_type)); + BLI_assert(functions != nullptr); + + functions->convert_single_to_uninitialized(from_value, to_value); +} + +void DataTypeConversions::convert_to_initialized_n(fn::GSpan from_span, + fn::GMutableSpan to_span) const +{ + const CPPType &from_type = from_span.type(); + const CPPType &to_type = to_span.type(); + BLI_assert(from_span.size() == to_span.size()); + BLI_assert(this->is_convertible(from_type, to_type)); + const fn::MultiFunction *fn = this->get_conversion_multi_function( + MFDataType::ForSingle(from_type), MFDataType::ForSingle(to_type)); + fn::MFParamsBuilder params{*fn, from_span.size()}; + params.add_readonly_single_input(from_span); + to_type.destruct_n(to_span.data(), to_span.size()); + params.add_uninitialized_single_output(to_span); + fn::MFContextBuilder context; + fn->call_auto(IndexRange(from_span.size()), params, context); +} + +class GVArray_For_ConvertedGVArray : public fn::GVArrayImpl { + private: + fn::GVArray varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + + public: + GVArray_For_ConvertedGVArray(fn::GVArray varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : fn::GVArrayImpl(to_type, varray.size()), + varray_(std::move(varray)), + from_type_(varray_.type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + } + + private: + void get(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_.get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_.get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } +}; + +class GVMutableArray_For_ConvertedGVMutableArray : public fn::GVMutableArrayImpl { + private: + fn::GVMutableArray varray_; + const CPPType &from_type_; + ConversionFunctions old_to_new_conversions_; + ConversionFunctions new_to_old_conversions_; + + public: + GVMutableArray_For_ConvertedGVMutableArray(fn::GVMutableArray varray, + const CPPType &to_type, + const DataTypeConversions &conversions) + : fn::GVMutableArrayImpl(to_type, varray.size()), + varray_(std::move(varray)), + from_type_(varray_.type()) + { + old_to_new_conversions_ = *conversions.get_conversion_functions(from_type_, to_type); + new_to_old_conversions_ = *conversions.get_conversion_functions(to_type, from_type_); + } + + private: + void get(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_.get(index, buffer); + old_to_new_conversions_.convert_single_to_initialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void get_to_uninitialized(const int64_t index, void *r_value) const override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + varray_.get(index, buffer); + old_to_new_conversions_.convert_single_to_uninitialized(buffer, r_value); + from_type_.destruct(buffer); + } + + void set_by_move(const int64_t index, void *value) override + { + BUFFER_FOR_CPP_TYPE_VALUE(from_type_, buffer); + new_to_old_conversions_.convert_single_to_uninitialized(value, buffer); + varray_.set_by_relocate(index, buffer); + } +}; + +fn::GVArray DataTypeConversions::try_convert(fn::GVArray varray, const CPPType &to_type) const +{ + const CPPType &from_type = varray.type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return fn::GVArray::For<GVArray_For_ConvertedGVArray>(std::move(varray), to_type, *this); +} + +fn::GVMutableArray DataTypeConversions::try_convert(fn::GVMutableArray varray, + const CPPType &to_type) const +{ + const CPPType &from_type = varray.type(); + if (from_type == to_type) { + return varray; + } + if (!this->is_convertible(from_type, to_type)) { + return {}; + } + return fn::GVMutableArray::For<GVMutableArray_For_ConvertedGVMutableArray>( + std::move(varray), to_type, *this); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/undo_system.c b/source/blender/blenkernel/intern/undo_system.c index db5184edfd2..3e263fafe28 100644 --- a/source/blender/blenkernel/intern/undo_system.c +++ b/source/blender/blenkernel/intern/undo_system.c @@ -61,13 +61,39 @@ static CLG_LogRef LOG = {"bke.undosys"}; /* -------------------------------------------------------------------- */ +/** \name Undo Types + * \{ */ + +const UndoType *BKE_UNDOSYS_TYPE_IMAGE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_MEMFILE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_PAINTCURVE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_PARTICLE = NULL; +const UndoType *BKE_UNDOSYS_TYPE_SCULPT = NULL; +const UndoType *BKE_UNDOSYS_TYPE_TEXT = NULL; + +static ListBase g_undo_types = {NULL, NULL}; + +static const UndoType *BKE_undosys_type_from_context(bContext *C) +{ + LISTBASE_FOREACH (const UndoType *, ut, &g_undo_types) { + /* No poll means we don't check context. */ + if (ut->poll && ut->poll(C)) { + return ut; + } + } + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Internal Nested Undo Checks * * Make sure we're not running undo operations from 'step_encode', 'step_decode' callbacks. * bugs caused by this situation aren't _that_ hard to spot but aren't always so obvious. * Best we have a check which shows the problem immediately. - * * \{ */ + #define WITH_NESTED_UNDO_CHECK #ifdef WITH_NESTED_UNDO_CHECK @@ -90,36 +116,9 @@ static bool g_undo_callback_running = false; # define UNDO_NESTED_CHECK_BEGIN ((void)0) # define UNDO_NESTED_CHECK_END ((void)0) #endif -/** \} */ -/* -------------------------------------------------------------------- */ -/** \name Public Undo Types - * - * Unfortunately we need this for a handful of places. - * \{ */ -const UndoType *BKE_UNDOSYS_TYPE_IMAGE = NULL; -const UndoType *BKE_UNDOSYS_TYPE_MEMFILE = NULL; -const UndoType *BKE_UNDOSYS_TYPE_PAINTCURVE = NULL; -const UndoType *BKE_UNDOSYS_TYPE_PARTICLE = NULL; -const UndoType *BKE_UNDOSYS_TYPE_SCULPT = NULL; -const UndoType *BKE_UNDOSYS_TYPE_TEXT = NULL; /** \} */ -/* UndoType */ - -static ListBase g_undo_types = {NULL, NULL}; - -static const UndoType *BKE_undosys_type_from_context(bContext *C) -{ - LISTBASE_FOREACH (const UndoType *, ut, &g_undo_types) { - /* No poll means we don't check context. */ - if (ut->poll && ut->poll(C)) { - return ut; - } - } - return NULL; -} - /* -------------------------------------------------------------------- */ /** \name Internal Callback Wrappers * @@ -346,7 +345,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); @@ -358,7 +357,6 @@ void BKE_undosys_stack_init_from_main(UndoStack *ustack, struct Main *bmain) undosys_stack_push_main(ustack, IFACE_("Original"), bmain); } -/* called after 'BKE_undosys_stack_init_from_main' */ void BKE_undosys_stack_init_from_context(UndoStack *ustack, bContext *C) { const UndoType *ut = BKE_undosys_type_from_context(C); @@ -367,7 +365,6 @@ void BKE_undosys_stack_init_from_context(UndoStack *ustack, bContext *C) } } -/* name optional */ bool BKE_undosys_stack_has_undo(const UndoStack *ustack, const char *name) { if (name) { @@ -397,10 +394,6 @@ UndoStep *BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const Un return BKE_undosys_stack_active_with_type(ustack, ut); } -/** - * \param steps: Limit the number of undo steps. - * \param memory_limit: Limit the amount of memory used by the undo stack. - */ void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size_t memory_limit) { UNDO_NESTED_ASSERT(false); @@ -455,6 +448,10 @@ void BKE_undosys_stack_limit_steps_and_memory(UndoStack *ustack, int steps, size /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Undo Step + * \{ */ + UndoStep *BKE_undosys_step_push_init_with_type(UndoStack *ustack, bContext *C, const char *name, @@ -497,20 +494,17 @@ UndoStep *BKE_undosys_step_push_init(UndoStack *ustack, bContext *C, const char return BKE_undosys_step_push_init_with_type(ustack, C, name, ut); } -/** - * \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 +596,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 : @@ -613,9 +607,6 @@ UndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char return BKE_undosys_step_push_with_type(ustack, C, name, ut); } -/** - * Useful when we want to diff against previous undo data but can't be sure the types match. - */ UndoStep *BKE_undosys_step_same_type_next(UndoStep *us) { if (us) { @@ -629,9 +620,6 @@ UndoStep *BKE_undosys_step_same_type_next(UndoStep *us) return us; } -/** - * Useful when we want to diff against previous undo data but can't be sure the types match. - */ UndoStep *BKE_undosys_step_same_type_prev(UndoStep *us) { if (us) { @@ -674,14 +662,6 @@ UndoStep *BKE_undosys_step_find_by_type(UndoStack *ustack, const UndoType *ut) return NULL; } -/** - * Return direction of the undo/redo from `us_reference` (or `ustack->step_active` if NULL), and - * `us_target`. - * - * \note If `us_reference` and `us_target` are the same, we consider this is an undo. - * - * \return -1 for undo, 1 for redo, 0 in case of error. - */ eUndoStepDir BKE_undosys_step_calc_direction(const UndoStack *ustack, const UndoStep *us_target, const UndoStep *us_reference) @@ -741,19 +721,6 @@ static UndoStep *undosys_step_iter_first(UndoStep *us_reference, const eUndoStep 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 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). - */ bool BKE_undosys_step_load_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, @@ -842,37 +809,22 @@ bool BKE_undosys_step_load_data_ex(UndoStack *ustack, return false; } -/** - * Undo/Redo until the given `us_target` step becomes the active (currently loaded) one. - */ bool BKE_undosys_step_load_data(UndoStack *ustack, bContext *C, UndoStep *us_target) { /* Note that here we do not skip 'skipped' steps by default. */ return BKE_undosys_step_load_data_ex(ustack, C, us_target, NULL, false); } -/** - * Undo/Redo until the step matching given `index` in the undo stack becomes the active (currently - * loaded) one. - */ void BKE_undosys_step_load_from_index(UndoStack *ustack, bContext *C, const int index) { UndoStep *us_target = BLI_findlink(&ustack->steps, index); BLI_assert(us_target->skip == false); + if (us_target == ustack->step_active) { + return; + } BKE_undosys_step_load_data(ustack, C, us_target); } -/** - * Undo until `us_target` step becomes the active (currently loaded) one. - * - * \warning This function assumes that the given target step is _before_ current active one. - * - * \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 **before** the given one (if - * the given one has to be skipped). - */ bool BKE_undosys_step_undo_with_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, @@ -888,19 +840,11 @@ bool BKE_undosys_step_undo_with_data_ex(UndoStack *ustack, return BKE_undosys_step_load_data_ex(ustack, C, us_target, us_reference, use_skip); } -/** - * Undo until `us_target` step becomes the active (currently loaded) one. - * - * \note See #BKE_undosys_step_undo_with_data_ex for details. - */ bool BKE_undosys_step_undo_with_data(UndoStack *ustack, bContext *C, UndoStep *us_target) { return BKE_undosys_step_undo_with_data_ex(ustack, C, us_target, true); } -/** - * Undo one step from current active (currently loaded) one. - */ bool BKE_undosys_step_undo(UndoStack *ustack, bContext *C) { if (ustack->step_active != NULL) { @@ -909,17 +853,6 @@ bool BKE_undosys_step_undo(UndoStack *ustack, bContext *C) return false; } -/** - * Redo until `us_target` step becomes the active (currently loaded) one. - * - * \warning This function assumes that the given target step is _after_ current active one. - * - * \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 **after** the given one (if - * the given one has to be skipped). - */ bool BKE_undosys_step_redo_with_data_ex(UndoStack *ustack, bContext *C, UndoStep *us_target, @@ -934,19 +867,11 @@ bool BKE_undosys_step_redo_with_data_ex(UndoStack *ustack, return BKE_undosys_step_load_data_ex(ustack, C, us_target, us_reference, use_skip); } -/** - * Redo until `us_target` step becomes the active (currently loaded) one. - * - * \note See #BKE_undosys_step_redo_with_data_ex for details. - */ bool BKE_undosys_step_redo_with_data(UndoStack *ustack, bContext *C, UndoStep *us_target) { return BKE_undosys_step_redo_with_data_ex(ustack, C, us_target, true); } -/** - * Redo one step from current active one. - */ bool BKE_undosys_step_redo(UndoStack *ustack, bContext *C) { if (ustack->step_active != NULL) { @@ -955,9 +880,6 @@ bool BKE_undosys_step_redo(UndoStack *ustack, bContext *C) return false; } -/** - * Similar to #WM_operatortype_append - */ UndoType *BKE_undosys_type_append(void (*undosys_fn)(UndoType *)) { UndoType *ut; diff --git a/source/blender/blenkernel/intern/unit.c b/source/blender/blenkernel/intern/unit.c index 4e9a3c9fb2e..fde3ac13ceb 100644 --- a/source/blender/blenkernel/intern/unit.c +++ b/source/blender/blenkernel/intern/unit.c @@ -1091,22 +1091,6 @@ double BKE_unit_apply_preferred_unit(const struct UnitSettings *settings, int ty return value * scalar + bias; } -/** - * Make a copy of the string that replaces the units with numbers. - * - * This is only used when evaluating user input and can afford to be a bit slower - * - * This is to be used before python evaluation so.. - * 10.1km -> 10.1*1000.0 - * ...will be resolved by python. - * - * Values will be split by an add sign. - * 5'2" -> 5*0.3048 + 2*0.0254 - * - * \param str_prev: is optional, when valid it is used to get a base unit when none is set. - * - * \return True of a change was made. - */ bool BKE_unit_replace_string( char *str, int len_max, const char *str_prev, double scale_pref, int system, int type) { @@ -1196,7 +1180,6 @@ bool BKE_unit_replace_string( return changed; } -/* 45µm --> 45um */ void BKE_unit_name_to_alt(char *str, int len_max, const char *orig_str, int system, int type) { const bUnitCollection *usys = unit_get_system(system, type); diff --git a/source/blender/blenkernel/intern/font.c b/source/blender/blenkernel/intern/vfont.c index 0e159418724..4a598288ee6 100644 --- a/source/blender/blenkernel/intern/font.c +++ b/source/blender/blenkernel/intern/vfont.c @@ -40,7 +40,6 @@ #include "BLI_string_utf8.h" #include "BLI_threads.h" #include "BLI_utildefines.h" -#include "BLI_vfontdata.h" #include "BLT_translation.h" @@ -50,13 +49,15 @@ #include "DNA_vfont_types.h" #include "BKE_anim_path.h" +#include "BKE_bpath.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" @@ -77,7 +78,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; @@ -107,7 +108,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); } } @@ -123,6 +124,21 @@ static void vfont_free_data(ID *id) } } +static void vfont_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + VFont *vfont = (VFont *)id; + + if (vfont->packedfile != NULL && (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0) { + return; + } + + if (BKE_vfont_is_builtin(vfont)) { + return; + } + + BKE_bpath_foreach_path_fixed_process(bpath_data, vfont->filepath); +} + static void vfont_blend_write(BlendWriter *writer, ID *id, const void *id_address) { VFont *vf = (VFont *)id; @@ -162,6 +178,7 @@ IDTypeInfo IDType_ID_VF = { .name_plural = "fonts", .translation_context = BLT_I18NCONTEXT_ID_VFONT, .flags = IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = vfont_init_data, .copy_data = vfont_copy_data, @@ -169,6 +186,7 @@ IDTypeInfo IDType_ID_VF = { .make_local = NULL, .foreach_id = NULL, .foreach_cache = NULL, + .foreach_path = vfont_foreach_path, .owner_get = NULL, .blend_write = vfont_blend_write, @@ -183,7 +201,6 @@ IDTypeInfo IDType_ID_VF = { /***************************** VFont *******************************/ -/* The vfont code */ void BKE_vfont_free_data(struct VFont *vfont) { if (vfont->data) { @@ -300,7 +317,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,19 +352,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) { /* 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; 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)); } @@ -694,7 +711,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 */ }; /* -------------------------------------------------------------------- */ @@ -803,7 +820,7 @@ static bool vfont_to_curve(Object *ob, 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"); @@ -954,7 +971,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); } @@ -1136,7 +1153,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; @@ -1500,7 +1517,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; @@ -1743,10 +1760,6 @@ bool BKE_vfont_to_curve_nubase(Object *ob, int mode, ListBase *r_nubase) return BKE_vfont_to_curve_ex(ob, ob->data, mode, r_nubase, NULL, NULL, NULL, NULL); } -/** - * Warning: expects to have access to evaluated data - * (i.e. passed object should be evaluated one...). - */ bool BKE_vfont_to_curve(Object *ob, int mode) { Curve *cu = ob->data; diff --git a/source/blender/blenkernel/intern/vfontdata_freetype.c b/source/blender/blenkernel/intern/vfontdata_freetype.c new file mode 100644 index 00000000000..9b79d5635d1 --- /dev/null +++ b/source/blender/blenkernel/intern/vfontdata_freetype.c @@ -0,0 +1,550 @@ +/* + * 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; +} + +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 0b9ef5c537d..39a7725bfa3 100644 --- a/source/blender/blenkernel/intern/volume.cc +++ b/source/blender/blenkernel/intern/volume.cc @@ -28,18 +28,20 @@ #include "BLI_compiler_compat.h" #include "BLI_fileops.h" -#include "BLI_float3.hh" #include "BLI_float4x4.hh" #include "BLI_ghash.h" #include "BLI_index_range.hh" #include "BLI_map.hh" #include "BLI_math.h" +#include "BLI_math_vec_types.hh" #include "BLI_path_util.h" #include "BLI_string.h" +#include "BLI_string_ref.hh" #include "BLI_task.hh" #include "BLI_utildefines.h" #include "BKE_anim_data.h" +#include "BKE_bpath.h" #include "BKE_geometry_set.hh" #include "BKE_global.h" #include "BKE_idtype.h" @@ -71,6 +73,7 @@ static CLG_LogRef LOG = {"bke.volume"}; using blender::float3; using blender::float4x4; using blender::IndexRange; +using blender::StringRef; #ifdef WITH_OPENVDB # include <atomic> @@ -135,11 +138,19 @@ static struct VolumeFileCache { } std::lock_guard<std::mutex> lock(mutex); - return simplified_grids.lookup_or_add_cb(simplify_level, [&]() { - const float resolution_factor = 1.0f / (1 << simplify_level); - const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(*grid); - return BKE_volume_grid_create_with_changed_resolution(grid_type, *grid, resolution_factor); + openvdb::GridBase::Ptr simple_grid; + + /* Isolate creating grid since that's multithreaded and we are + * holding a mutex lock. */ + blender::threading::isolate_task([&] { + simple_grid = simplified_grids.lookup_or_add_cb(simplify_level, [&]() { + const float resolution_factor = 1.0f / (1 << simplify_level); + const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(*grid); + return BKE_volume_grid_create_with_changed_resolution( + grid_type, *grid, resolution_factor); + }); }); + return simple_grid; } /* Unique key: filename + grid name. */ @@ -244,16 +255,20 @@ static struct VolumeFileCache { protected: void update_for_remove_user(Entry &entry) { - if (entry.num_metadata_users + entry.num_tree_users == 0) { - cache.erase(entry); - } - else if (entry.num_tree_users == 0) { - /* Note we replace the grid rather than clearing, so that if there is - * any other shared pointer to the grid it will keep the tree. */ - entry.grid = entry.grid->copyGridWithNewTree(); - entry.simplified_grids.clear(); - entry.is_loaded = false; - } + /* Isolate file unloading since that's multithreaded and we are + * holding a mutex lock. */ + blender::threading::isolate_task([&] { + if (entry.num_metadata_users + entry.num_tree_users == 0) { + cache.erase(entry); + } + else if (entry.num_tree_users == 0) { + /* Note we replace the grid rather than clearing, so that if there is + * any other shared pointer to the grid it will keep the tree. */ + entry.grid = entry.grid->copyGridWithNewTree(); + entry.simplified_grids.clear(); + entry.is_loaded = false; + } + }); } /* Cache contents */ @@ -534,7 +549,7 @@ static void volume_copy_data(Main *UNUSED(bmain), #ifdef WITH_OPENVDB if (volume_src->runtime.grids) { const VolumeGridVector &grids_src = *(volume_src->runtime.grids); - volume_dst->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector, grids_src); + volume_dst->runtime.grids = MEM_new<VolumeGridVector>(__func__, grids_src); } #endif @@ -548,7 +563,8 @@ static void volume_free_data(ID *id) BKE_volume_batch_cache_free(volume); MEM_SAFE_FREE(volume->mat); #ifdef WITH_OPENVDB - OBJECT_GUARDED_SAFE_DELETE(volume->runtime.grids, VolumeGridVector); + MEM_delete(volume->runtime.grids); + volume->runtime.grids = nullptr; #endif } @@ -556,7 +572,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); } } @@ -574,6 +590,18 @@ static void volume_foreach_cache(ID *id, function_callback(id, &key, (void **)&volume->runtime.grids, 0, user_data); } +static void volume_foreach_path(ID *id, BPathForeachPathData *bpath_data) +{ + Volume *volume = reinterpret_cast<Volume *>(id); + + if (volume->packedfile != nullptr && + (bpath_data->flag & BKE_BPATH_FOREACH_PATH_SKIP_PACKED) != 0) { + return; + } + + BKE_bpath_foreach_path_fixed_process(bpath_data, volume->filepath); +} + static void volume_blend_write(BlendWriter *writer, ID *id, const void *id_address) { Volume *volume = (Volume *)id; @@ -643,6 +671,7 @@ IDTypeInfo IDType_ID_VO = { /* name_plural */ "volumes", /* translation_context */ BLT_I18NCONTEXT_ID_VOLUME, /* flags */ IDTYPE_FLAGS_APPEND_IS_REUSABLE, + /* asset_type_info */ nullptr, /* init_data */ volume_init_data, /* copy_data */ volume_copy_data, @@ -650,6 +679,7 @@ IDTypeInfo IDType_ID_VO = { /* make_local */ nullptr, /* foreach_id */ volume_foreach_id, /* foreach_cache */ volume_foreach_cache, + /* foreach_path */ volume_foreach_path, /* owner_get */ nullptr, /* blend_write */ volume_blend_write, @@ -666,7 +696,7 @@ void BKE_volume_init_grids(Volume *volume) { #ifdef WITH_OPENVDB if (volume->runtime.grids == nullptr) { - volume->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector); + volume->runtime.grids = MEM_new<VolumeGridVector>(__func__); } #else UNUSED_VARS(volume); @@ -937,7 +967,7 @@ BoundBox *BKE_volume_boundbox_get(Object *ob) } if (ob->runtime.bb == nullptr) { - ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), __func__); + ob->runtime.bb = MEM_cnew<BoundBox>(__func__); } const Volume *volume = (Volume *)ob->data; @@ -1112,16 +1142,16 @@ void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, co if (!grids->is_loaded()) { /* No grids loaded in CoW datablock, nothing lost by discarding. */ - OBJECT_GUARDED_DELETE(grids, VolumeGridVector); + MEM_delete(grids); } else if (!STREQ(volume->filepath, filepath)) { /* Filepath changed, discard grids from CoW datablock. */ - OBJECT_GUARDED_DELETE(grids, VolumeGridVector); + MEM_delete(grids); } else { /* Keep grids from CoW datablock. We might still unload them a little * later in BKE_volume_eval_geometry if the frame changes. */ - OBJECT_GUARDED_DELETE(volume->runtime.grids, VolumeGridVector); + MEM_delete(volume->runtime.grids); volume->runtime.grids = grids; } #else @@ -1223,7 +1253,6 @@ const VolumeGrid *BKE_volume_grid_active_get_for_read(const Volume *volume) return BKE_volume_grid_get_for_read(volume, index); } -/* Tries to find a grid with the given name. Make sure that the volume has been loaded. */ const VolumeGrid *BKE_volume_grid_find_for_read(const Volume *volume, const char *name) { int num_grids = BKE_volume_num_grids(volume); @@ -1363,7 +1392,6 @@ int BKE_volume_grid_channels(const VolumeGrid *grid) return 0; } -/* Transformation from index space to object space. */ void BKE_volume_grid_transform_matrix(const VolumeGrid *volume_grid, float mat[4][4]) { #ifdef WITH_OPENVDB @@ -1451,6 +1479,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 @@ -1513,11 +1556,6 @@ bool BKE_volume_grid_bounds(openvdb::GridBase::ConstPtr grid, float3 &r_min, flo return true; } -/** - * Return a new grid pointer with only the metadata and transform changed. - * This is useful for instances, where there is a separate transform on top of the original - * grid transform that must be applied for some operations that only take a grid argument. - */ openvdb::GridBase::ConstPtr BKE_volume_grid_shallow_transform(openvdb::GridBase::ConstPtr grid, const blender::float4x4 &transform) { diff --git a/source/blender/blenkernel/intern/volume_render.cc b/source/blender/blenkernel/intern/volume_render.cc index 6dc497bb616..c0a205b5673 100644 --- a/source/blender/blenkernel/intern/volume_render.cc +++ b/source/blender/blenkernel/intern/volume_render.cc @@ -21,8 +21,8 @@ #include "MEM_guardedalloc.h" #include "BLI_array.hh" -#include "BLI_float3.hh" #include "BLI_math_matrix.h" +#include "BLI_math_vec_types.hh" #include "BLI_math_vector.h" #include "BLI_vector.hh" diff --git a/source/blender/blenkernel/intern/volume_to_mesh.cc b/source/blender/blenkernel/intern/volume_to_mesh.cc index e9d6eea4614..336ce724e35 100644 --- a/source/blender/blenkernel/intern/volume_to_mesh.cc +++ b/source/blender/blenkernel/intern/volume_to_mesh.cc @@ -16,7 +16,7 @@ #include <vector> -#include "BLI_float3.hh" +#include "BLI_math_vec_types.hh" #include "BLI_span.hh" #include "BLI_utildefines.h" @@ -121,46 +121,57 @@ struct VolumeToMeshOp { } }; -static Mesh *new_mesh_from_openvdb_data(Span<openvdb::Vec3s> verts, - Span<openvdb::Vec3I> tris, - Span<openvdb::Vec4I> quads) +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_normals_tag_dirty(mesh); - return mesh; +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 +179,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 3c168a6c7b2..e3fe1e04368 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -82,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); } } @@ -187,6 +187,7 @@ IDTypeInfo IDType_ID_WS = { .name_plural = "workspaces", .translation_context = BLT_I18NCONTEXT_ID_WORKSPACE, .flags = IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_ONLY_APPEND | IDTYPE_FLAGS_NO_ANIMDATA, + .asset_type_info = NULL, .init_data = workspace_init_data, .copy_data = NULL, @@ -194,6 +195,7 @@ IDTypeInfo IDType_ID_WS = { .make_local = NULL, .foreach_id = workspace_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = workspace_blend_write, @@ -319,13 +321,6 @@ WorkSpace *BKE_workspace_add(Main *bmain, const char *name) return new_workspace; } -/** - * Remove \a workspace by freeing itself and its data. This is a higher-level wrapper that - * calls #workspace_free_data (through #BKE_id_free) to free the workspace data, and frees - * other data-blocks owned by \a workspace and its layouts (currently that is screens only). - * - * Always use this to remove (and free) workspaces. Don't free non-ID workspace members here. - */ void BKE_workspace_remove(Main *bmain, WorkSpace *workspace) { for (WorkSpaceLayout *layout = workspace->layouts.first, *layout_next; layout; @@ -369,9 +364,6 @@ void BKE_workspace_instance_hook_free(const Main *bmain, WorkSpaceInstanceHook * MEM_freeN(hook); } -/** - * Add a new layout to \a workspace for \a screen. - */ WorkSpaceLayout *BKE_workspace_layout_add(Main *bmain, WorkSpace *workspace, bScreen *screen, @@ -434,13 +426,6 @@ WorkSpaceLayout *BKE_workspace_layout_find(const WorkSpace *workspace, const bSc return NULL; } -/** - * Find the layout for \a screen without knowing which workspace to look in. - * Can also be used to find the workspace that contains \a screen. - * - * \param r_workspace: Optionally return the workspace that contains the - * looked up layout (if found). - */ WorkSpaceLayout *BKE_workspace_layout_find_global(const Main *bmain, const bScreen *screen, WorkSpace **r_workspace) @@ -464,15 +449,6 @@ WorkSpaceLayout *BKE_workspace_layout_find_global(const Main *bmain, return NULL; } -/** - * Circular workspace layout iterator. - * - * \param callback: Custom function which gets executed for each layout. - * Can return false to stop iterating. - * \param arg: Custom data passed to each \a callback call. - * - * \return the layout at which \a callback returned false. - */ WorkSpaceLayout *BKE_workspace_layout_iter_circular(const WorkSpace *workspace, WorkSpaceLayout *start, bool (*callback)(const WorkSpaceLayout *layout, @@ -564,18 +540,11 @@ void BKE_workspace_active_set(WorkSpaceInstanceHook *hook, WorkSpace *workspace) } } -/** - * Get the layout that is active for \a hook (which is the visible layout for the active workspace - * in \a hook). - */ WorkSpaceLayout *BKE_workspace_active_layout_get(const WorkSpaceInstanceHook *hook) { return hook->act_layout; } -/** - * Get the layout to be activated should \a workspace become or be the active workspace in \a hook. - */ WorkSpaceLayout *BKE_workspace_active_layout_for_workspace_get(const WorkSpaceInstanceHook *hook, const WorkSpace *workspace) { @@ -588,17 +557,6 @@ WorkSpaceLayout *BKE_workspace_active_layout_for_workspace_get(const WorkSpaceIn return workspace_relation_get_data_matching_parent(&workspace->hook_layout_relations, hook); } -/** - * \brief Activate a layout - * - * Sets \a layout as active for \a workspace when activated through or already active in \a hook. - * So when the active workspace of \a hook is \a workspace, \a layout becomes the active layout of - * \a hook too. See #BKE_workspace_active_set(). - * - * \a workspace does not need to be active for this. - * - * WorkSpaceInstanceHook.act_layout should only be modified directly to update the layout pointer. - */ void BKE_workspace_active_layout_set(WorkSpaceInstanceHook *hook, const int winid, WorkSpace *workspace, diff --git a/source/blender/blenkernel/intern/world.c b/source/blender/blenkernel/intern/world.c index fe03c5b817a..b2e90b7ba12 100644 --- a/source/blender/blenkernel/intern/world.c +++ b/source/blender/blenkernel/intern/world.c @@ -131,7 +131,8 @@ 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)); } } @@ -191,6 +192,7 @@ IDTypeInfo IDType_ID_WO = { .name_plural = "worlds", .translation_context = BLT_I18NCONTEXT_ID_WORLD, .flags = IDTYPE_FLAGS_APPEND_IS_REUSABLE, + .asset_type_info = NULL, .init_data = world_init_data, .copy_data = world_copy_data, @@ -198,6 +200,7 @@ IDTypeInfo IDType_ID_WO = { .make_local = NULL, .foreach_id = world_foreach_id, .foreach_cache = NULL, + .foreach_path = NULL, .owner_get = NULL, .blend_write = world_blend_write, diff --git a/source/blender/blenkernel/intern/writeavi.c b/source/blender/blenkernel/intern/writeavi.c index 4635db98514..a4f20f980b4 100644 --- a/source/blender/blenkernel/intern/writeavi.c +++ b/source/blender/blenkernel/intern/writeavi.c @@ -315,7 +315,6 @@ static void context_free_avi(void *context_v) #endif /* WITH_AVI */ -/* similar to BKE_image_path_from_imformat() */ void BKE_movie_filepath_get(char *string, const RenderData *rd, bool preview, const char *suffix) { bMovieHandle *mh = BKE_movie_handle_get(rd->im_format.imtype); diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index a20c918c517..4d94132e6fd 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -87,6 +87,7 @@ typedef struct FFMpegContext { AVStream *video_stream; AVStream *audio_stream; AVFrame *current_frame; /* Image frame in output pixel format. */ + int video_time; /* Image frame in Blender's own pixel format, may need conversion to the output pixel format. */ AVFrame *img_convert_frame; @@ -96,6 +97,7 @@ typedef struct FFMpegContext { uint8_t *audio_deinterleave_buffer; int audio_input_samples; double audio_time; + double audio_time_total; bool audio_deinterleave; int audio_sample_size; @@ -318,14 +320,15 @@ static const char **get_file_extensions(int format) } /* Write a frame to the output file */ -static int write_video_frame(FFMpegContext *context, int cfra, AVFrame *frame, ReportList *reports) +static int write_video_frame(FFMpegContext *context, AVFrame *frame, ReportList *reports) { int ret, success = 1; AVPacket *packet = av_packet_alloc(); AVCodecContext *c = context->video_codec; - frame->pts = cfra; + frame->pts = context->video_time; + context->video_time++; ret = avcodec_send_frame(c, frame); if (ret < 0) { @@ -804,6 +807,8 @@ static AVStream *alloc_video_stream(FFMpegContext *context, avcodec_parameters_from_context(st->codecpar, c); + context->video_time = 0.0f; + return st; } @@ -1397,9 +1402,10 @@ static void write_audio_frames(FFMpegContext *context, double to_pts) AVCodecContext *c = context->audio_codec; while (context->audio_stream) { - if ((context->audio_time >= to_pts) || !write_audio_frame(context)) { + if ((context->audio_time_total >= to_pts) || !write_audio_frame(context)) { break; } + context->audio_time_total += (double)context->audio_input_samples / (double)c->sample_rate; context->audio_time += (double)context->audio_input_samples / (double)c->sample_rate; } } @@ -1423,22 +1429,25 @@ int BKE_ffmpeg_append(void *context_v, if (context->video_stream) { avframe = generate_video_frame(context, (unsigned char *)pixels); - success = (avframe && write_video_frame(context, frame - start_frame, avframe, reports)); + success = (avframe && write_video_frame(context, avframe, reports)); +# ifdef WITH_AUDASPACE + /* 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)); +# else + UNUSED_VARS(start_frame); +# endif if (context->ffmpeg_autosplit) { if (avio_tell(context->outfile->pb) > FFMPEG_AUTOSPLIT_SIZE) { end_ffmpeg_impl(context, true); context->ffmpeg_autosplit_count++; + success &= start_ffmpeg_impl(context, rd, rectx, recty, suffix, reports); } } } -# ifdef WITH_AUDASPACE - /* 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; } @@ -1881,6 +1890,7 @@ void *BKE_ffmpeg_context_create(void) context->ffmpeg_autosplit_count = 0; context->ffmpeg_preview = false; context->stamp_data = NULL; + context->audio_time_total = 0.0; return context; } diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h index 3e13ba602a4..c338540b5f5 100644 --- a/source/blender/blenkernel/nla_private.h +++ b/source/blender/blenkernel/nla_private.h @@ -165,46 +165,77 @@ typedef struct NlaKeyframingContext { /* --------------- NLA Functions (not to be used as a proper API) ----------------------- */ -/* convert from strip time <-> global time */ +/** + * Convert non clipped mapping for strip-time <-> global time: + * `mode = eNlaTime_ConvertModes[] -> NLATIME_CONVERT_*` + * + * Only secure for 'internal' (i.e. within AnimSys evaluation) operations, + * but should not be directly relied on for stuff which interacts with editors. + */ float nlastrip_get_frame(NlaStrip *strip, float cframe, short mode); /* --------------- NLA Evaluation (very-private stuff) ----------------------- */ /* these functions are only defined here to avoid problems with the order * in which they get defined. */ +/** + * Gets the strip active at the current time for a list of strips for evaluation purposes. + */ NlaEvalStrip *nlastrips_ctime_get_strip(ListBase *list, ListBase *strips, short index, const struct AnimationEvalContext *anim_eval_context, - const bool flush_to_original); + bool flush_to_original); +/** + * Evaluates the given evaluation strip. + */ void nlastrip_evaluate(PointerRNA *ptr, NlaEvalData *channels, ListBase *modifiers, NlaEvalStrip *nes, NlaEvalSnapshot *snapshot, const struct AnimationEvalContext *anim_eval_context, - const bool flush_to_original); + bool flush_to_original); +/** + * write the accumulated settings to. + */ void nladata_flush_channels(PointerRNA *ptr, NlaEvalData *channels, NlaEvalSnapshot *snapshot, - const bool flush_to_original); + bool flush_to_original); void nlasnapshot_enable_all_blend_domain(NlaEvalSnapshot *snapshot); void nlasnapshot_ensure_channels(NlaEvalData *eval_data, NlaEvalSnapshot *snapshot); +/** + * Blends the \a lower_snapshot with the \a upper_snapshot into \a r_blended_snapshot according + * to the given \a upper_blendmode and \a upper_influence. + * + * For \a upper_snapshot, blending limited to values in the \a blend_domain. + * For Replace blend-mode, this allows the upper snapshot to have a location XYZ channel + * where only a subset of values are blended. + */ void nlasnapshot_blend(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, NlaEvalSnapshot *upper_snapshot, - const short upper_blendmode, - const float upper_influence, + short upper_blendmode, + float upper_influence, NlaEvalSnapshot *r_blended_snapshot); +/** + * Using \a blended_snapshot and \a lower_snapshot, we can solve for the \a r_upper_snapshot. + * + * Only channels that exist within \a blended_snapshot are inverted. + * + * For \a r_upper_snapshot, disables \a NlaEvalChannelSnapshot->remap_domain for failed inversions. + * Only values within the \a remap_domain are processed. + */ void nlasnapshot_blend_get_inverted_upper_snapshot(NlaEvalData *eval_data, NlaEvalSnapshot *lower_snapshot, NlaEvalSnapshot *blended_snapshot, - const short upper_blendmode, - const float upper_influence, + short upper_blendmode, + float upper_influence, NlaEvalSnapshot *r_upper_snapshot); #ifdef __cplusplus diff --git a/source/blender/blenkernel/tracking_private.h b/source/blender/blenkernel/tracking_private.h index 8de6ec93a7c..0c1f73fa4b6 100644 --- a/source/blender/blenkernel/tracking_private.h +++ b/source/blender/blenkernel/tracking_private.h @@ -78,12 +78,21 @@ void tracking_get_search_origin_frame_pixel(int frame_width, const struct MovieTrackingMarker *marker, float frame_pixel[2]); +/** + * Each marker has 5 coordinates associated with it that get warped with + * tracking: the four corners ("pattern_corners"), and the center ("pos"). + * This function puts those 5 points into the appropriate frame for tracking + * (the "search" coordinate frame). + */ void tracking_get_marker_coords_for_tracking(int frame_width, int frame_height, const struct MovieTrackingMarker *marker, double search_pixel_x[5], double search_pixel_y[5]); +/** + * Inverse of #tracking_get_marker_coords_for_tracking. + */ void tracking_set_marker_coords_from_tracking(int frame_width, int frame_height, struct MovieTrackingMarker *marker, @@ -92,11 +101,23 @@ void tracking_set_marker_coords_from_tracking(int frame_width, /*********************** General purpose utility functions *************************/ +/** + * Place a disabled marker before or after specified ref_marker. + * + * If before is truth, disabled marker is placed before reference + * one, and it's placed after it otherwise. + * + * If there's already a marker at the frame where disabled one is expected to be placed, + * nothing will happen if overwrite is false. + */ void tracking_marker_insert_disabled(struct MovieTrackingTrack *track, const struct MovieTrackingMarker *ref_marker, bool before, bool overwrite); +/** + * Fill in Libmv C-API camera intrinsics options from tracking structure. + */ void tracking_cameraIntrinscisOptionsFromTracking( struct MovieTracking *tracking, int calibration_width, @@ -109,17 +130,26 @@ void tracking_trackingCameraFromIntrinscisOptions( struct libmv_TrackRegionOptions; +/** + * Fill in libmv tracker options structure with settings need to be used to perform track. + */ void tracking_configure_tracker(const MovieTrackingTrack *track, float *mask, bool is_backwards, struct libmv_TrackRegionOptions *options); +/** + * Get previous keyframed marker. + */ struct MovieTrackingMarker *tracking_get_keyframed_marker(struct MovieTrackingTrack *track, int current_frame, bool backwards); /*********************** Masking *************************/ +/** + * Region is in pixel space, relative to marker's center. + */ float *tracking_track_get_mask_for_region(int frame_width, int frame_height, const float region_min[2], @@ -145,11 +175,13 @@ typedef struct TrackingImageAccessor { SpinLock cache_lock; } TrackingImageAccessor; -/* Clips are used to access images of an actual footage. +/** + * Clips are used to access images of an actual footage. * Tracks are used to access masks associated with the tracks. * - * NOTE: Both clips and tracks arrays are copied into the image accessor. It means that the caller - * is allowed to pass temporary arrays which are only valid during initialization. */ + * \note Both clips and tracks arrays are copied into the image accessor. It means that the caller + * is allowed to pass temporary arrays which are only valid during initialization. + */ TrackingImageAccessor *tracking_image_accessor_new(MovieClip *clips[MAX_ACCESSOR_CLIP], int num_clips, MovieTrackingTrack **tracks, |