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
This commit is contained in:
Sergey Sharybin 2014-03-26 16:55:20 +06:00
parent 97cb76a45d
commit 6e5e3b73f3
4 changed files with 583 additions and 0 deletions

View File

@ -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__ */

View File

@ -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

View File

@ -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

View File

@ -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;