diff options
author | Sergey Sharybin <sergey.vfx@gmail.com> | 2014-03-26 14:55:20 +0400 |
---|---|---|
committer | Sergey Sharybin <sergey.vfx@gmail.com> | 2014-03-28 15:14:11 +0400 |
commit | 6e5e3b73f37f952420d87a3d8acd07a7f68dd5a3 (patch) | |
tree | 243d196eb5a4e8f3ac04d8b13c1517eedb2d1e28 | |
parent | 97cb76a45d689a0d39e1d194f51119412e6c8e00 (diff) |
Fix T37599: Crash making linked objects local and undo
Root of the issues comes to the fact that it's possible to produce
a situation when library object data uses local object. This is
actually forbidden and not supported by .blend IO.
Made it so Make Local wouldn't produce such an unsupported states.
Reviewers: brecht
Differential Revision: https://developer.blender.org/D372
-rw-r--r-- | source/blender/blenkernel/BKE_library_query.h | 57 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/library_query.c | 460 | ||||
-rw-r--r-- | source/blender/editors/object/object_relations.c | 64 |
4 files changed, 583 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_library_query.h b/source/blender/blenkernel/BKE_library_query.h new file mode 100644 index 00000000000..50958f8ee80 --- /dev/null +++ b/source/blender/blenkernel/BKE_library_query.h @@ -0,0 +1,57 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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) 2014 by Blender Foundation. + * All rights reserved. + * + * Contributor(s): Sergey SHarybin. + * + * ***** END GPL LICENSE BLOCK ***** + */ +#ifndef __BKE_LIBRARY_QUERY_H__ +#define __BKE_LIBRARY_QUERY_H__ + +/** \file BKE_library_query.h + * \ingroup bke + * \since March 2014 + * \author sergey + */ + +struct ID; + +/* Tips for the callback for cases it's gonna to modify the pointer. */ +enum { + IDWALK_NOP = 0, + IDWALK_NEVER_NULL = (1 << 0), + IDWALK_NEVER_SELF = (1 << 1), +}; + +/* Call a callback for each ID link which the given ID uses. + * + * Return 'false' if you want to stop iteration. + */ +typedef bool (*LibraryIDLinkCallback) (void *user_data, struct ID **id_pointer, int cd_flag); + +/* Flags for the foreach function itself. */ +enum { + IDWALK_READONLY = (1 << 0), +}; + +/* Loop over all of the ID's this datablock links to. */ +void BKE_library_foreach_ID_link(struct ID *id, LibraryIDLinkCallback callback, void *user_data, int flag); + +#endif /* __BKE_LIBRARY_QUERY_H__ */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 2a0f642996d..749a5eb408d 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -107,6 +107,7 @@ set(SRC intern/lamp.c intern/lattice.c intern/library.c + intern/library_query.c intern/linestyle.c intern/mask.c intern/mask_evaluate.c @@ -214,6 +215,7 @@ set(SRC BKE_lamp.h BKE_lattice.h BKE_library.h + BKE_library_query.h BKE_linestyle.h BKE_main.h BKE_mask.h diff --git a/source/blender/blenkernel/intern/library_query.c b/source/blender/blenkernel/intern/library_query.c new file mode 100644 index 00000000000..f67b1ac8796 --- /dev/null +++ b/source/blender/blenkernel/intern/library_query.c @@ -0,0 +1,460 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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) 2014 by Blender Foundation. + * All rights reserved. + * + * Contributor(s): Sergey Sharybin. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/blenkernel/intern/library_query.c + * \ingroup bke + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_brush_types.h" +#include "DNA_camera_types.h" +#include "DNA_constraint_types.h" +#include "DNA_group_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_key_types.h" +#include "DNA_lamp_types.h" +#include "DNA_lattice_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meta_types.h" +#include "DNA_movieclip_types.h" +#include "DNA_mask_types.h" +#include "DNA_node_types.h" +#include "DNA_object_force.h" +#include "DNA_sequence_types.h" +#include "DNA_screen_types.h" +#include "DNA_speaker_types.h" +#include "DNA_sound_types.h" +#include "DNA_vfont_types.h" +#include "DNA_world_types.h" + +#include "BLI_utildefines.h" + +#include "BKE_animsys.h" +#include "BKE_constraint.h" +#include "BKE_fcurve.h" +#include "BKE_library_query.h" +#include "BKE_modifier.h" +#include "BKE_particle.h" +#include "BKE_sequencer.h" +#include "BKE_tracking.h" + +#define FOREACH_CALLBACK_INVOKE_ID_PP(self_id, id_pp, flag, callback, user_data, cb_flag) \ + { \ + ID *old_id = *id_pp; \ + bool keep_working = callback(user_data, id_pp, cb_flag); \ + if (flag & IDWALK_READONLY) { \ + BLI_assert(*id_pp == old_id); \ + } \ + if (keep_working == false) { \ + /* REAL DANGER! Beware of this return! */ \ + /* TODO(sergey): Make it less creepy without too much duplicated code.. */ \ + return; \ + } \ + } (void) 0 + +#define FOREACH_CALLBACK_INVOKE_ID(self_id, id, flag, callback, user_data, cb_flag) \ + FOREACH_CALLBACK_INVOKE_ID_PP(self_id, &(id), flag, callback, user_data, cb_flag) \ + +#define FOREACH_CALLBACK_INVOKE(self_id, id_super, flag, callback, user_data, cb_flag) \ + { \ + CHECK_TYPE(&((id_super)->id), ID *); \ + FOREACH_CALLBACK_INVOKE_ID_PP(self_id, (ID **)&id_super, flag, callback, user_data, cb_flag); \ + } (void) 0 + +typedef struct LibraryForeachIDData { + ID *self_id; + int flag; + LibraryIDLinkCallback callback; + void *user_data; +} LibraryForeachIDData; + +static void library_foreach_modifiersForeachIDLink(void *user_data, Object *UNUSED(object), + ID **id_pointer) +{ + LibraryForeachIDData *data = (LibraryForeachIDData *) user_data; + FOREACH_CALLBACK_INVOKE_ID_PP(data->self_id, id_pointer, data->flag, data->callback, data->user_data, IDWALK_NOP); +} + +static void library_foreach_constraintObjectLooper(bConstraint *UNUSED(con), ID **id_pointer, + short UNUSED(isReference), void *user_data) +{ + LibraryForeachIDData *data = (LibraryForeachIDData *) user_data; + FOREACH_CALLBACK_INVOKE_ID_PP(data->self_id, id_pointer, data->flag, data->callback, data->user_data, IDWALK_NOP); +} + +static void library_foreach_animationData(LibraryForeachIDData *data, AnimData *adt) +{ + FCurve *fcu; + + for (fcu = adt->drivers.first; fcu; fcu = fcu->next) { + ChannelDriver *driver = fcu->driver; + DriverVar *dvar; + + for (dvar = driver->variables.first; dvar; dvar = dvar->next) { + /* only used targets */ + DRIVER_TARGETS_USED_LOOPER(dvar) + { + FOREACH_CALLBACK_INVOKE_ID(data->self_id, dtar->id, data->flag, data->callback, data->user_data, IDWALK_NOP); + } + DRIVER_TARGETS_LOOPER_END + } + } +} + + +static void library_foreach_mtex(LibraryForeachIDData *data, MTex *mtex) +{ + FOREACH_CALLBACK_INVOKE(data->self_id, mtex->object, data->flag, data->callback, data->user_data, IDWALK_NOP); + FOREACH_CALLBACK_INVOKE(data->self_id, mtex->tex, data->flag, data->callback, data->user_data, IDWALK_NOP); +} + + +/** + * Loop over all of the ID's this datablock links to. + * + * \note: May be extended to be recursive in the future. + */ +void BKE_library_foreach_ID_link(ID *id, LibraryIDLinkCallback callback, void *user_data, int flag) +{ + AnimData *adt; + LibraryForeachIDData data; + int i; + + data.self_id = id; + data.flag = flag; + data.callback = callback; + data.user_data = user_data; + + adt = BKE_animdata_from_id(id); + if (adt) { + library_foreach_animationData(&data, adt); + } + +#define CALLBACK_INVOKE_ID(check_id, cb_flag) \ + FOREACH_CALLBACK_INVOKE_ID(id, check_id, flag, callback, user_data, cb_flag) + +#define CALLBACK_INVOKE(check_id_super, cb_flag) \ + FOREACH_CALLBACK_INVOKE(id, check_id_super, flag, callback, user_data, cb_flag) + + switch (GS(id->name)) { + case ID_SCE: + { + Scene *scene = (Scene *) id; + Base *base; + + CALLBACK_INVOKE(scene->camera, IDWALK_NOP); + CALLBACK_INVOKE(scene->world, IDWALK_NOP); + CALLBACK_INVOKE(scene->set, IDWALK_NOP); + if (scene->basact) { + CALLBACK_INVOKE(scene->basact->object, IDWALK_NOP); + } + CALLBACK_INVOKE(scene->obedit, IDWALK_NOP); + + if (scene->ed) { + Sequence *seq; + SEQP_BEGIN(scene->ed, seq) + { + CALLBACK_INVOKE(seq->scene, IDWALK_NOP); + CALLBACK_INVOKE(seq->scene_camera, IDWALK_NOP); + CALLBACK_INVOKE(seq->clip, IDWALK_NOP); + CALLBACK_INVOKE(seq->mask, IDWALK_NOP); + } + SEQ_END + } + + CALLBACK_INVOKE(scene->gpd, IDWALK_NOP); + + for (base = scene->base.first; base; base = base->next) { + CALLBACK_INVOKE(base->object, IDWALK_NOP); + } + } + + case ID_OB: + { + Object *object = (Object *) id; + CALLBACK_INVOKE(object->parent, IDWALK_NOP); + CALLBACK_INVOKE(object->track, IDWALK_NOP); + CALLBACK_INVOKE(object->proxy, IDWALK_NOP); + CALLBACK_INVOKE(object->proxy_group, IDWALK_NOP); + CALLBACK_INVOKE(object->proxy_from, IDWALK_NOP); + CALLBACK_INVOKE(object->poselib, IDWALK_NOP); + for (i = 0; i < object->totcol; i++) { + CALLBACK_INVOKE(object->mat[i], IDWALK_NOP); + } + CALLBACK_INVOKE(object->gpd, IDWALK_NOP); + CALLBACK_INVOKE(object->dup_group, IDWALK_NOP); + if (object->particlesystem.first) { + ParticleSystem *particle_system; + for (particle_system = object->particlesystem.first; + particle_system; + particle_system = particle_system->next) + { + CALLBACK_INVOKE(particle_system->target_ob, IDWALK_NOP); + CALLBACK_INVOKE(particle_system->parent, IDWALK_NOP); + } + } + + if (object->pose) { + bPoseChannel *pose_channel; + for (pose_channel = object->pose->chanbase.first; + pose_channel; + pose_channel = pose_channel->next) + { + CALLBACK_INVOKE(pose_channel->custom, IDWALK_NOP); + BKE_id_loop_constraints(&pose_channel->constraints, + library_foreach_constraintObjectLooper, + &data); + } + } + + modifiers_foreachIDLink(object, + library_foreach_modifiersForeachIDLink, + &data); + BKE_id_loop_constraints(&object->constraints, + library_foreach_constraintObjectLooper, + &data); + break; + } + + case ID_ME: + { + Mesh *mesh = (Mesh *) id; + CALLBACK_INVOKE(mesh->texcomesh, IDWALK_NOP); + CALLBACK_INVOKE(mesh->key, IDWALK_NOP); + for (i = 0; i < mesh->totcol; i++) { + CALLBACK_INVOKE(mesh->mat[i], IDWALK_NOP); + } + break; + } + + case ID_CU: + { + Curve *curve = (Curve *) id; + CALLBACK_INVOKE(curve->bevobj, IDWALK_NOP); + CALLBACK_INVOKE(curve->taperobj, IDWALK_NOP); + CALLBACK_INVOKE(curve->textoncurve, IDWALK_NOP); + CALLBACK_INVOKE(curve->key, IDWALK_NOP); + for (i = 0; i < curve->totcol; i++) { + CALLBACK_INVOKE(curve->mat[i], IDWALK_NOP); + } + CALLBACK_INVOKE(curve->vfont, IDWALK_NOP); + CALLBACK_INVOKE(curve->vfontb, IDWALK_NOP); + CALLBACK_INVOKE(curve->vfonti, IDWALK_NOP); + CALLBACK_INVOKE(curve->vfontbi, IDWALK_NOP); + break; + } + + case ID_MB: + { + MetaBall *metaball = (MetaBall *) id; + for (i = 0; i < metaball->totcol; i++) { + CALLBACK_INVOKE(metaball->mat[i], IDWALK_NOP); + } + break; + } + + case ID_MA: + { + Material *material = (Material *) id; + for (i = 0; i < MAX_MTEX; i++) { + if (material->mtex[i]) { + library_foreach_mtex(&data, material->mtex[i]); + } + } + CALLBACK_INVOKE(material->nodetree, IDWALK_NOP); + CALLBACK_INVOKE(material->group, IDWALK_NOP); + break; + } + + case ID_TE: + { + Tex *texture = (Tex *) id; + CALLBACK_INVOKE(texture->nodetree, IDWALK_NOP); + CALLBACK_INVOKE(texture->ima, IDWALK_NOP); + break; + } + + case ID_LT: + { + Lattice *lattice = (Lattice *) id; + CALLBACK_INVOKE(lattice->key, IDWALK_NOP); + break; + } + + case ID_LA: + { + Lamp *lamp = (Lamp *) id; + for (i = 0; i < MAX_MTEX; i++) { + if (lamp->mtex[i]) { + library_foreach_mtex(&data, lamp->mtex[i]); + } + } + CALLBACK_INVOKE(lamp->nodetree, IDWALK_NOP); + break; + } + + case ID_CA: + { + Camera *camera = (Camera *) id; + CALLBACK_INVOKE(camera->dof_ob, IDWALK_NOP); + break; + } + + case ID_KE: + { + Key *key = (Key *) id; + CALLBACK_INVOKE_ID(key->from, IDWALK_NOP); + break; + } + + case ID_SCR: + { + bScreen *screen = (bScreen *) id; + CALLBACK_INVOKE(screen->scene, IDWALK_NOP); + } + + case ID_WO: + { + World *world = (World *) id; + for (i = 0; i < MAX_MTEX; i++) { + if (world->mtex[i]) { + library_foreach_mtex(&data, world->mtex[i]); + } + } + CALLBACK_INVOKE(world->nodetree, IDWALK_NOP); + break; + } + + case ID_SPK: + { + Speaker *speaker = (Speaker *) id; + CALLBACK_INVOKE(speaker->sound, IDWALK_NOP); + break; + } + + case ID_GR: + { + Group *group = (Group *) id; + GroupObject *group_object; + for (group_object = group->gobject.first; + group_object; + group_object = group_object->next) + { + CALLBACK_INVOKE(group_object->ob, IDWALK_NOP); + } + break; + } + + case ID_NT: + { + bNodeTree *ntree = (bNodeTree *) id; + bNode *node; + for (node = ntree->nodes.first; node; node = node->next) { + CALLBACK_INVOKE_ID(node->id, IDWALK_NOP); + } + break; + } + + case ID_BR: + { + Brush *brush = (Brush *) id; + CALLBACK_INVOKE(brush->toggle_brush, IDWALK_NOP); + library_foreach_mtex(&data, &brush->mtex); + library_foreach_mtex(&data, &brush->mask_mtex); + break; + } + + case ID_PA: + { + ParticleSettings *particle_settings = (ParticleSettings *) id; + CALLBACK_INVOKE(particle_settings->dup_group, IDWALK_NOP); + CALLBACK_INVOKE(particle_settings->dup_ob, IDWALK_NOP); + CALLBACK_INVOKE(particle_settings->bb_ob, IDWALK_NOP); + if (particle_settings->effector_weights) { + CALLBACK_INVOKE(particle_settings->effector_weights->group, IDWALK_NOP); + } + break; + } + + case ID_MC: + { + MovieClip *clip = (MovieClip *) id; + MovieTracking *tracking = &clip->tracking; + MovieTrackingObject *object; + CALLBACK_INVOKE(clip->gpd, IDWALK_NOP); + for (object = tracking->objects.first; + object; + object = object->next) + { + ListBase *tracksbase = BKE_tracking_object_get_tracks(tracking, object); + MovieTrackingTrack *track; + for (track = tracksbase->first; + track; + track = track->next) + { + CALLBACK_INVOKE(track->gpd, IDWALK_NOP); + } + } + break; + } + + case ID_MSK: + { + Mask *mask = (Mask *) id; + MaskLayer *mask_layer; + for (mask_layer = mask->masklayers.first; + mask_layer; + mask_layer = mask_layer->next) + { + MaskSpline *mask_spline; + + CALLBACK_INVOKE_ID(mask_spline->parent.id, IDWALK_NOP); + + for (mask_spline = mask_layer->splines.first; + mask_spline; + mask_spline = mask_spline->next) + { + int i; + for (i = 0; i < mask_spline->tot_point; i++) { + MaskSplinePoint *point = &mask_spline->points[i]; + CALLBACK_INVOKE_ID(point->parent.id, IDWALK_NOP); + } + } + } + break; + } + } + + #undef CALLBACK_INVOKE_ID + #undef CALLBACK_INVOKE +} + +#undef FOREACH_CALLBACK_INVOKE_ID +#undef FOREACH_CALLBACK_INVOKE diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 5fd6fcfaa47..c1adabf0a94 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -76,6 +76,7 @@ #include "BKE_lamp.h" #include "BKE_lattice.h" #include "BKE_library.h" +#include "BKE_library_query.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_mball.h" @@ -2080,6 +2081,56 @@ enum { MAKE_LOCAL_ALL }; +static bool tag_localizable_looper(void *UNUSED(user_data), ID **id_pointer, int UNUSED(cd_flag)) +{ + if (*id_pointer) { + (*id_pointer)->flag &= ~LIB_DOIT; + } + return true; +} + +static void tag_localizable_objects(bContext *C, int mode) +{ + Main *bmain = CTX_data_main(C); + Object *object; + + BKE_main_id_tag_all(bmain, false); + + /* Set LIB_DOIT flag for all selected objects, so next we can check whether + * object is gonna to become local or not. + */ + CTX_DATA_BEGIN (C, Object *, object, selected_objects) + { + object->id.flag |= LIB_DOIT; + + /* If data is also gonna to become local, mark data we're interested in + * as gonna-to-be-local. + */ + if (mode == MAKE_LOCAL_SELECT_OBDATA) { + ID *data_id = (ID *) object->data; + data_id->flag |= LIB_DOIT; + } + } + CTX_DATA_END; + + /* Also forbid making objects local if other library objects are using + * them for modifiers or constraints. + */ + for (object = bmain->object.first; object; object = object->id.next) { + if ((object->id.flag & LIB_DOIT) == 0) { + BKE_library_foreach_ID_link(&object->id, tag_localizable_looper, NULL, IDWALK_READONLY); + } + if (object->data) { + ID *data_id = (ID *) object->data; + if ((data_id->flag & LIB_DOIT) == 0) { + BKE_library_foreach_ID_link(data_id, tag_localizable_looper, NULL, IDWALK_READONLY); + } + } + } + + /* TODO(sergey): Drivers targets? */ +} + static int make_local_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -2096,10 +2147,15 @@ static int make_local_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } + tag_localizable_objects(C, mode); BKE_main_id_clear_newpoins(bmain); CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob->id.flag & LIB_DOIT) == 0) { + continue; + } + if (ob->id.lib) id_make_local(&ob->id, false); } @@ -2116,6 +2172,10 @@ static int make_local_exec(bContext *C, wmOperator *op) CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob->id.flag & LIB_DOIT) == 0) { + continue; + } + id = ob->data; if (id && (ELEM(mode, MAKE_LOCAL_SELECT_OBDATA, MAKE_LOCAL_SELECT_OBDATA_MATERIAL))) { @@ -2145,6 +2205,10 @@ static int make_local_exec(bContext *C, wmOperator *op) if (mode == MAKE_LOCAL_SELECT_OBDATA_MATERIAL) { CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if ((ob->id.flag & LIB_DOIT) == 0) { + continue; + } + if (ob->type == OB_LAMP) { la = ob->data; |