bugfix [#20639] BF25_SVN_25888 and below - OBJ and 3DS import fails
blender supports type changing for textures in a way that python doesnt. add a new general function. Example usage: tex = bpy.data.textures.new("Foo") tex.type = 'IMAGE' tex = tex.recast_type() Macro to give the number of users accounting for fake user. ID_REAL_USERS(id) Use this so you can remove a datablock if it has a fake users as well as apply transformations to it in the 3D view. Move api function bpy.data.add_texture() --> bpy.data.textures.new()/remove()
This commit is contained in:
@@ -507,8 +507,9 @@ def process_next_chunk(file, previous_chunk, importedObjects, IMAGE_SEARCH):
|
||||
return [float(col)/255 for col in struct.unpack('<3B', temp_data)] # data [0,1,2] == rgb
|
||||
|
||||
def read_texture(new_chunk, temp_chunk, name, mapto):
|
||||
new_texture = bpy.data.add_texture('Diffuse')
|
||||
new_texture = bpy.data.textures.new('Diffuse')
|
||||
new_texture.type = 'IMAGE'
|
||||
new_texture = new_texture.recast_type()
|
||||
|
||||
img = None
|
||||
while (new_chunk.bytes_read < new_chunk.length):
|
||||
|
@@ -367,8 +367,9 @@ def create_materials(filepath, material_libs, unique_materials, unique_material_
|
||||
#==================================================================================#
|
||||
def load_material_image(blender_material, context_material_name, imagepath, type):
|
||||
|
||||
texture= bpy.data.add_texture(type)
|
||||
texture= bpy.data.textures.new(type)
|
||||
texture.type= 'IMAGE'
|
||||
texture = texture.recast_type() # Workaround for limitation in rna api.
|
||||
# texture= bpy.data.textures.new(type)
|
||||
# texture.setType('Image')
|
||||
|
||||
|
@@ -47,7 +47,7 @@ int get_defgroup_num (struct Object *ob, struct bDeformGroup *dg);
|
||||
int *get_defgroup_flip_map(struct Object *ob);
|
||||
int get_named_vertexgroup_num (Object *ob, const char *name);
|
||||
void unique_vertexgroup_name (struct bDeformGroup *dg, struct Object *ob);
|
||||
void flip_vertexgroup_name (char *name_r, const char *name, int strip_number);
|
||||
void flip_vertexgroup_name (char *name, const char *from_name, int strip_number);
|
||||
|
||||
float deformvert_get_weight(const struct MDeformVert *dvert, int group_num);
|
||||
float vertexgroup_get_vertex_weight(const struct MDeformVert *dvert, int index, int group_num);
|
||||
|
@@ -411,7 +411,7 @@ static int apply_objects_internal(bContext *C, ReportList *reports, int apply_lo
|
||||
if(ob->type==OB_MESH) {
|
||||
me= ob->data;
|
||||
|
||||
if(me->id.us>1) {
|
||||
if(ID_REAL_USERS(me) > 1) {
|
||||
BKE_report(reports, RPT_ERROR, "Can't apply to a multi user mesh, doing nothing.");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
@@ -419,7 +419,7 @@ static int apply_objects_internal(bContext *C, ReportList *reports, int apply_lo
|
||||
else if(ob->type==OB_ARMATURE) {
|
||||
arm= ob->data;
|
||||
|
||||
if(arm->id.us>1) {
|
||||
if(ID_REAL_USERS(arm) > 1) {
|
||||
BKE_report(reports, RPT_ERROR, "Can't apply to a multi user armature, doing nothing.");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
@@ -427,7 +427,7 @@ static int apply_objects_internal(bContext *C, ReportList *reports, int apply_lo
|
||||
else if(ELEM(ob->type, OB_CURVE, OB_SURF)) {
|
||||
cu= ob->data;
|
||||
|
||||
if(cu->id.us>1) {
|
||||
if(ID_REAL_USERS(cu) > 1) {
|
||||
BKE_report(reports, RPT_ERROR, "Can't apply to a multi user curve, doing nothing.");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
@@ -984,7 +984,7 @@ static int object_origin_set_exec(bContext *C, wmOperator *op)
|
||||
|
||||
if (arm->id.lib) {
|
||||
tot_lib_error++;
|
||||
} else if(arm->id.us>1) {
|
||||
} else if(ID_REAL_USERS(arm) > 1) {
|
||||
/*BKE_report(op->reports, RPT_ERROR, "Can't apply to a multi user armature");
|
||||
return;*/
|
||||
tot_multiuser_arm_error++;
|
||||
|
@@ -198,6 +198,8 @@ typedef struct PreviewImage {
|
||||
/* fluidsim Ipo */
|
||||
#define ID_FLUIDSIM MAKE_ID2('F', 'S')
|
||||
|
||||
#define ID_REAL_USERS(id) (((ID *)id)->us - ((((ID *)id)->flag & LIB_FAKEUSER) ? 1:0))
|
||||
|
||||
/* id->flag: set frist 8 bits always at zero while reading */
|
||||
#define LIB_LOCAL 0
|
||||
#define LIB_EXTERN 1
|
||||
|
@@ -569,6 +569,7 @@ void RNA_id_pointer_create(struct ID *id, PointerRNA *r_ptr);
|
||||
void RNA_pointer_create(struct ID *id, StructRNA *type, void *data, PointerRNA *r_ptr);
|
||||
|
||||
void RNA_blender_rna_pointer_create(PointerRNA *r_ptr);
|
||||
void RNA_pointer_recast(PointerRNA *ptr, PointerRNA *r_ptr);
|
||||
|
||||
extern PointerRNA PointerRNA_NULL;
|
||||
|
||||
|
@@ -64,6 +64,8 @@ extern EnumPropertyItem operator_return_items[];
|
||||
|
||||
extern EnumPropertyItem brush_sculpt_tool_items[];
|
||||
|
||||
extern EnumPropertyItem texture_type_items[];
|
||||
|
||||
extern EnumPropertyItem unpack_method_items[];
|
||||
|
||||
extern EnumPropertyItem object_type_items[];
|
||||
|
@@ -335,6 +335,7 @@ typedef struct ExtensionRNA {
|
||||
#define MainTexts Main
|
||||
#define MainActions Main
|
||||
#define MainGroups Main
|
||||
#define MainTextures Main
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@@ -191,6 +191,30 @@ PointerRNA rna_pointer_inherit_refine(PointerRNA *ptr, StructRNA *type, void *da
|
||||
return result;
|
||||
}
|
||||
|
||||
/**/
|
||||
void RNA_pointer_recast(PointerRNA *ptr, PointerRNA *r_ptr)
|
||||
{
|
||||
#if 0 // works but this case if covered by more general code below.
|
||||
if(RNA_struct_is_ID(ptr->type)) {
|
||||
/* simple case */
|
||||
RNA_id_pointer_create(ptr->id.data, r_ptr);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
StructRNA *base;
|
||||
PointerRNA t_ptr;
|
||||
*r_ptr= *ptr; /* initialize as the same incase cant recast */
|
||||
|
||||
for(base=ptr->type->base; base; base=base->base) {
|
||||
t_ptr= rna_pointer_inherit_refine(ptr, base, ptr->data);
|
||||
if(t_ptr.type && t_ptr.type != ptr->type) {
|
||||
*r_ptr= t_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ID Properties */
|
||||
|
||||
/* return a UI local ID prop definition for this prop */
|
||||
|
@@ -57,6 +57,7 @@
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_text_types.h"
|
||||
#include "DNA_texture_types.h"
|
||||
#include "DNA_group_types.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
@@ -66,8 +67,6 @@ Tex *rna_Main_add_texture(Main *bmain, char *name)
|
||||
return add_texture(name);
|
||||
}
|
||||
|
||||
/* TODO: remove texture? */
|
||||
|
||||
Image *rna_Main_add_image(Main *bmain, char *filename)
|
||||
{
|
||||
return BKE_add_image_file(filename, 0);
|
||||
@@ -79,10 +78,10 @@ Camera *rna_Main_cameras_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_cameras_remove(Main *bmain, ReportList *reports, struct Camera *camera)
|
||||
{
|
||||
if(camera->id.us == 0)
|
||||
if(ID_REAL_USERS(camera) == 0)
|
||||
free_libblock(&bmain->camera, camera);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Camera \"%s\" must have zero users to be removed, found %d.", camera->id.name+2, camera->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Camera \"%s\" must have zero users to be removed, found %d.", camera->id.name+2, ID_REAL_USERS(camera));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
@@ -130,10 +129,10 @@ void rna_Main_objects_remove(Main *bmain, ReportList *reports, struct Object *ob
|
||||
# don't do this since ob is already freed!
|
||||
bpy.data.remove_object(ob)
|
||||
*/
|
||||
if(object->id.us == 0)
|
||||
if(ID_REAL_USERS(object) == 0)
|
||||
free_libblock(&bmain->object, object);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Object \"%s\" must have zero users to be removed, found %d.", object->id.name+2, object->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Object \"%s\" must have zero users to be removed, found %d.", object->id.name+2, ID_REAL_USERS(object));
|
||||
}
|
||||
|
||||
struct Material *rna_Main_materials_new(Main *bmain, char* name)
|
||||
@@ -142,10 +141,10 @@ struct Material *rna_Main_materials_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_materials_remove(Main *bmain, ReportList *reports, struct Material *material)
|
||||
{
|
||||
if(material->id.us == 0)
|
||||
if(ID_REAL_USERS(material) == 0)
|
||||
free_libblock(&bmain->mat, material);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Material \"%s\" must have zero users to be removed, found %d.", material->id.name+2, material->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Material \"%s\" must have zero users to be removed, found %d.", material->id.name+2, ID_REAL_USERS(material));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
@@ -158,10 +157,10 @@ Mesh *rna_Main_meshes_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_meshes_remove(Main *bmain, ReportList *reports, Mesh *mesh)
|
||||
{
|
||||
if(mesh->id.us == 0)
|
||||
if(ID_REAL_USERS(mesh) == 0)
|
||||
free_libblock(&bmain->mesh, mesh);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Mesh \"%s\" must have zero users to be removed, found %d.", mesh->id.name+2, mesh->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Mesh \"%s\" must have zero users to be removed, found %d.", mesh->id.name+2, ID_REAL_USERS(mesh));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
@@ -174,14 +173,28 @@ Lamp *rna_Main_lamps_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_lamps_remove(Main *bmain, ReportList *reports, Lamp *lamp)
|
||||
{
|
||||
if(lamp->id.us == 0)
|
||||
if(ID_REAL_USERS(lamp) == 0)
|
||||
free_libblock(&bmain->lamp, lamp);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Lamp \"%s\" must have zero users to be removed, found %d.", lamp->id.name+2, lamp->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Lamp \"%s\" must have zero users to be removed, found %d.", lamp->id.name+2, ID_REAL_USERS(lamp));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
|
||||
Tex *rna_Main_textures_new(Main *bmain, char* name)
|
||||
{
|
||||
Tex *tex= add_texture(name);
|
||||
tex->id.us--;
|
||||
return tex;
|
||||
}
|
||||
void rna_Main_textures_remove(Main *bmain, ReportList *reports, struct Tex *tex)
|
||||
{
|
||||
if(ID_REAL_USERS(tex) == 0)
|
||||
free_libblock(&bmain->tex, tex);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Texture \"%s\" must have zero users to be removed, found %d.", tex->id.name+2, ID_REAL_USERS(tex));
|
||||
}
|
||||
|
||||
Group *rna_Main_groups_new(Main *bmain, char* name)
|
||||
{
|
||||
return add_group(name);
|
||||
@@ -221,10 +234,10 @@ bArmature *rna_Main_armatures_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_armatures_remove(Main *bmain, ReportList *reports, bArmature *arm)
|
||||
{
|
||||
if(arm->id.us == 0)
|
||||
if(ID_REAL_USERS(arm) == 0)
|
||||
free_libblock(&bmain->armature, arm);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Armature \"%s\" must have zero users to be removed, found %d.", arm->id.name+2, arm->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Armature \"%s\" must have zero users to be removed, found %d.", arm->id.name+2, ID_REAL_USERS(arm));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
@@ -238,10 +251,10 @@ bAction *rna_Main_actions_new(Main *bmain, char* name)
|
||||
}
|
||||
void rna_Main_actions_remove(Main *bmain, ReportList *reports, bAction *act)
|
||||
{
|
||||
if(act->id.us == 0)
|
||||
if(ID_REAL_USERS(act) == 0)
|
||||
free_libblock(&bmain->action, act);
|
||||
else
|
||||
BKE_reportf(reports, RPT_ERROR, "Action \"%s\" must have zero users to be removed, found %d.", act->id.name+2, act->id.us);
|
||||
BKE_reportf(reports, RPT_ERROR, "Action \"%s\" must have zero users to be removed, found %d.", act->id.name+2, ID_REAL_USERS(act));
|
||||
|
||||
/* XXX python now has invalid pointer? */
|
||||
}
|
||||
@@ -253,12 +266,6 @@ void RNA_api_main(StructRNA *srna)
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
func= RNA_def_function(srna, "add_texture", "rna_Main_add_texture");
|
||||
RNA_def_function_ui_description(func, "Add a new texture.");
|
||||
parm= RNA_def_string(func, "name", "Tex", 0, "", "New name for the datablock."); /* optional */
|
||||
parm= RNA_def_pointer(func, "texture", "Texture", "", "New texture.");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func= RNA_def_function(srna, "add_image", "rna_Main_add_image");
|
||||
RNA_def_function_ui_description(func, "Add a new image.");
|
||||
parm= RNA_def_string(func, "filename", "", 0, "", "Filename to load image from.");
|
||||
@@ -457,7 +464,27 @@ void RNA_def_main_vfonts(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
}
|
||||
void RNA_def_main_textures(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "MainTextures");
|
||||
srna= RNA_def_struct(brna, "MainTextures", NULL);
|
||||
RNA_def_struct_ui_text(srna, "Main Textures", "Collection of groups.");
|
||||
|
||||
func= RNA_def_function(srna, "new", "rna_Main_textures_new");
|
||||
RNA_def_function_ui_description(func, "Add a new texture to the main database");
|
||||
parm= RNA_def_string(func, "name", "Texture", 0, "", "New name for the datablock.");
|
||||
RNA_def_property_flag(parm, PROP_REQUIRED);
|
||||
/* return type */
|
||||
parm= RNA_def_pointer(func, "texture", "Texture", "", "New texture datablock.");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func= RNA_def_function(srna, "remove", "rna_Main_textures_remove");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(func, "Remove a texture from the current blendfile.");
|
||||
parm= RNA_def_pointer(func, "texture", "Texture", "", "Texture to remove.");
|
||||
RNA_def_property_flag(parm, PROP_REQUIRED);
|
||||
}
|
||||
void RNA_def_main_brushes(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
|
@@ -51,6 +51,25 @@ static EnumPropertyItem texture_filter_items[] = {
|
||||
{TXF_SAT, "SAT", 0, "SAT (4x mem)", ""},
|
||||
{0, NULL, 0, NULL, NULL}};
|
||||
|
||||
EnumPropertyItem texture_type_items[] = {
|
||||
{0, "NONE", 0, "None", ""},
|
||||
{TEX_BLEND, "BLEND", ICON_TEXTURE, "Blend", ""},
|
||||
{TEX_CLOUDS, "CLOUDS", ICON_TEXTURE, "Clouds", ""},
|
||||
{TEX_DISTNOISE, "DISTORTED_NOISE", ICON_TEXTURE, "Distorted Noise", ""},
|
||||
{TEX_ENVMAP, "ENVIRONMENT_MAP", ICON_IMAGE_DATA, "Environment Map", ""},
|
||||
{TEX_IMAGE, "IMAGE", ICON_IMAGE_DATA, "Image or Movie", ""},
|
||||
{TEX_MAGIC, "MAGIC", ICON_TEXTURE, "Magic", ""},
|
||||
{TEX_MARBLE, "MARBLE", ICON_TEXTURE, "Marble", ""},
|
||||
{TEX_MUSGRAVE, "MUSGRAVE", ICON_TEXTURE, "Musgrave", ""},
|
||||
{TEX_NOISE, "NOISE", ICON_TEXTURE, "Noise", ""},
|
||||
{TEX_PLUGIN, "PLUGIN", ICON_PLUGIN, "Plugin", ""},
|
||||
{TEX_POINTDENSITY, "POINT_DENSITY", ICON_TEXTURE, "Point Density", ""},
|
||||
{TEX_STUCCI, "STUCCI", ICON_TEXTURE, "Stucci", ""},
|
||||
{TEX_VORONOI, "VORONOI", ICON_TEXTURE, "Voronoi", ""},
|
||||
{TEX_VOXELDATA, "VOXEL_DATA", ICON_TEXTURE, "Voxel Data", ""},
|
||||
{TEX_WOOD, "WOOD", ICON_TEXTURE, "Wood", ""},
|
||||
{0, NULL, 0, NULL, NULL}};
|
||||
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
@@ -1791,25 +1810,6 @@ static void rna_def_texture(BlenderRNA *brna)
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
static EnumPropertyItem prop_type_items[] = {
|
||||
{0, "NONE", 0, "None", ""},
|
||||
{TEX_BLEND, "BLEND", ICON_TEXTURE, "Blend", ""},
|
||||
{TEX_CLOUDS, "CLOUDS", ICON_TEXTURE, "Clouds", ""},
|
||||
{TEX_DISTNOISE, "DISTORTED_NOISE", ICON_TEXTURE, "Distorted Noise", ""},
|
||||
{TEX_ENVMAP, "ENVIRONMENT_MAP", ICON_IMAGE_DATA, "Environment Map", ""},
|
||||
{TEX_IMAGE, "IMAGE", ICON_IMAGE_DATA, "Image or Movie", ""},
|
||||
{TEX_MAGIC, "MAGIC", ICON_TEXTURE, "Magic", ""},
|
||||
{TEX_MARBLE, "MARBLE", ICON_TEXTURE, "Marble", ""},
|
||||
{TEX_MUSGRAVE, "MUSGRAVE", ICON_TEXTURE, "Musgrave", ""},
|
||||
{TEX_NOISE, "NOISE", ICON_TEXTURE, "Noise", ""},
|
||||
{TEX_PLUGIN, "PLUGIN", ICON_PLUGIN, "Plugin", ""},
|
||||
{TEX_POINTDENSITY, "POINT_DENSITY", ICON_TEXTURE, "Point Density", ""},
|
||||
{TEX_STUCCI, "STUCCI", ICON_TEXTURE, "Stucci", ""},
|
||||
{TEX_VORONOI, "VORONOI", ICON_TEXTURE, "Voronoi", ""},
|
||||
{TEX_VOXELDATA, "VOXEL_DATA", ICON_TEXTURE, "Voxel Data", ""},
|
||||
{TEX_WOOD, "WOOD", ICON_TEXTURE, "Wood", ""},
|
||||
{0, NULL, 0, NULL, NULL}};
|
||||
|
||||
srna= RNA_def_struct(brna, "Texture", "ID");
|
||||
RNA_def_struct_sdna(srna, "Tex");
|
||||
RNA_def_struct_ui_text(srna, "Texture", "Texture datablock used by materials, lamps, worlds and brushes.");
|
||||
@@ -1819,7 +1819,7 @@ static void rna_def_texture(BlenderRNA *brna)
|
||||
prop= RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
|
||||
//RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_enum_sdna(prop, NULL, "type");
|
||||
RNA_def_property_enum_items(prop, prop_type_items);
|
||||
RNA_def_property_enum_items(prop, texture_type_items);
|
||||
RNA_def_property_enum_funcs(prop, NULL, "rna_Texture_type_set", NULL);
|
||||
RNA_def_property_ui_text(prop, "Type", "");
|
||||
RNA_def_property_update(prop, 0, "rna_Texture_update");
|
||||
|
@@ -1713,6 +1713,13 @@ static PyObject *pyrna_struct_path_to_id(BPy_StructRNA *self, PyObject *args)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *pyrna_struct_recast_type(BPy_StructRNA *self, PyObject *args)
|
||||
{
|
||||
PointerRNA r_ptr;
|
||||
RNA_pointer_recast(&self->ptr, &r_ptr);
|
||||
return pyrna_struct_CreatePyObject(&r_ptr);
|
||||
}
|
||||
|
||||
static PyObject *pyrna_prop_path_to_id(BPy_PropertyRNA *self)
|
||||
{
|
||||
char *path;
|
||||
@@ -2537,6 +2544,7 @@ static struct PyMethodDef pyrna_struct_methods[] = {
|
||||
{"is_property_hidden", (PyCFunction)pyrna_struct_is_property_hidden, METH_VARARGS, NULL},
|
||||
{"path_resolve", (PyCFunction)pyrna_struct_path_resolve, METH_O, NULL},
|
||||
{"path_to_id", (PyCFunction)pyrna_struct_path_to_id, METH_VARARGS, NULL},
|
||||
{"recast_type", (PyCFunction)pyrna_struct_recast_type, METH_NOARGS, NULL},
|
||||
{"__dir__", (PyCFunction)pyrna_struct_dir, METH_NOARGS, NULL},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
Reference in New Issue
Block a user