forked from bartvdbraak/blender
bpy/rna api class feature
- python defined classes will be used when available (otherwise automaically generated metaclasses are made as before) - use properties rather then functions for python defined rna class's - call the classes getattr AFTER doing an RNA lookup, avoids setting and clearing exceptions for most attribute lookups, tested UI scripts are ~25% faster. - extending rna py classes this way is a nicer alternative to modifying the generated metaclasses in place. Example class --- snip class Object(bpy.types.ID): def _get_children(self): return [child for child in bpy.data.objects if child.parent == self] children = property(_get_children) --- snip The C initialization function looks in bpy_types.py for classes matching RNA structure names, using them when available. This means all objects in python will be instances of these classes. Python properties/funcs defined in ID py class will also be available for subclasses for eg. (Group Mesh etc)
This commit is contained in:
parent
30c4c4599d
commit
fac2ca1c7c
@ -1,72 +0,0 @@
|
||||
# ##### 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
def ord_ind(i1,i2):
|
||||
if i1<i2: return i1,i2
|
||||
return i2,i1
|
||||
|
||||
def edge_key(ed):
|
||||
v1, v2 = tuple(ed.verts)
|
||||
return ord_ind(v1, v2)
|
||||
|
||||
def face_edge_keys(face):
|
||||
verts = tuple(face.verts)
|
||||
if len(verts)==3:
|
||||
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[0])
|
||||
|
||||
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[3]), ord_ind(verts[3], verts[0])
|
||||
|
||||
def mesh_edge_keys(mesh):
|
||||
return [edge_key for face in mesh.faces for edge_key in face.edge_keys()]
|
||||
|
||||
def mesh_edge_face_count_dict(mesh, face_edge_keys=None):
|
||||
|
||||
# Optional speedup
|
||||
if face_edge_keys==None:
|
||||
face_edge_keys = [face.edge_keys() for face in face_list]
|
||||
|
||||
face_edge_count = {}
|
||||
for face_keys in face_edge_keys:
|
||||
for key in face_keys:
|
||||
try:
|
||||
face_edge_count[key] += 1
|
||||
except:
|
||||
face_edge_count[key] = 1
|
||||
|
||||
|
||||
return face_edge_count
|
||||
|
||||
def mesh_edge_face_count(mesh, face_edge_keys=None):
|
||||
edge_face_count_dict = mesh.edge_face_count_dict(face_edge_keys)
|
||||
return [edge_face_count_dict.get(ed.key(), 0) for ed in mesh.edges]
|
||||
|
||||
import bpy
|
||||
|
||||
# * Edge *
|
||||
class_obj = bpy.types.MeshEdge
|
||||
class_obj.key = edge_key
|
||||
|
||||
# * Face *
|
||||
class_obj = bpy.types.MeshFace
|
||||
class_obj.edge_keys = face_edge_keys
|
||||
|
||||
# * Mesh *
|
||||
class_obj = bpy.types.Mesh
|
||||
class_obj.edge_keys = mesh_edge_keys
|
||||
class_obj.edge_face_count = mesh_edge_face_count
|
||||
class_obj.edge_face_count_dict = mesh_edge_face_count_dict
|
@ -1,22 +0,0 @@
|
||||
# ##### 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
class_obj = bpy.types.Object
|
||||
|
||||
class_obj.getChildren = lambda self: [child for child in bpy.data.objects if child.parent == self]
|
@ -1,20 +0,0 @@
|
||||
# ##### 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy_ext.Object
|
||||
import bpy_ext.Mesh
|
82
release/scripts/modules/bpy_types.py
Normal file
82
release/scripts/modules/bpy_types.py
Normal file
@ -0,0 +1,82 @@
|
||||
# ##### 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
|
||||
StructRNA = bpy.types.Struct.__bases__[0]
|
||||
# StructRNA = bpy.types.Struct
|
||||
|
||||
|
||||
class Object(bpy.types.ID):
|
||||
|
||||
def _get_children(self):
|
||||
return [child for child in bpy.data.objects if child.parent == self]
|
||||
|
||||
children = property(_get_children)
|
||||
|
||||
|
||||
def ord_ind(i1,i2):
|
||||
if i1<i2: return i1,i2
|
||||
return i2,i1
|
||||
|
||||
class Mesh(bpy.types.ID):
|
||||
|
||||
def _get_edge_keys(self):
|
||||
return [edge_key for face in self.faces for edge_key in face.edge_keys]
|
||||
|
||||
edge_keys = property(_get_edge_keys)
|
||||
|
||||
def _get_edge_face_count_dict(self):
|
||||
face_edge_keys = [face.edge_keys for face in self.faces]
|
||||
face_edge_count = {}
|
||||
for face_keys in face_edge_keys:
|
||||
for key in face_keys:
|
||||
try:
|
||||
face_edge_count[key] += 1
|
||||
except:
|
||||
face_edge_count[key] = 1
|
||||
|
||||
return face_edge_count
|
||||
|
||||
edge_face_count_dict = property(_get_edge_face_count_dict)
|
||||
|
||||
def _get_edge_face_count(self):
|
||||
edge_face_count_dict = self.edge_face_count_dict
|
||||
return [edge_face_count_dict.get(ed.key, 0) for ed in mesh.edges]
|
||||
|
||||
edge_face_count = property(_get_edge_face_count)
|
||||
|
||||
|
||||
class MeshEdge(StructRNA):
|
||||
|
||||
def _get_key(self):
|
||||
return ord_ind(*tuple(self.verts))
|
||||
|
||||
key = property(_get_key)
|
||||
|
||||
|
||||
class MeshFace(StructRNA):
|
||||
|
||||
def _get_edge_keys(self):
|
||||
verts = tuple(self.verts)
|
||||
if len(verts)==3:
|
||||
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[0])
|
||||
|
||||
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[3]), ord_ind(verts[3], verts[0])
|
||||
|
||||
edge_keys = property(_get_edge_keys)
|
@ -28,9 +28,9 @@ def main(context):
|
||||
mesh = ob.data
|
||||
|
||||
face_list = [face for face in mesh.faces]
|
||||
face_edge_keys = [face.edge_keys() for face in face_list]
|
||||
face_edge_keys = [face.edge_keys for face in face_list]
|
||||
|
||||
edge_face_count = mesh.edge_face_count_dict(face_edge_keys)
|
||||
edge_face_count = mesh.edge_face_count_dict
|
||||
|
||||
def test_interior(index):
|
||||
for key in face_edge_keys[index]:
|
||||
|
@ -256,11 +256,11 @@ def getSelectedEdges(context, me, ob):
|
||||
if MESH_MODE == 'FACE':
|
||||
context.scene.tool_settings.mesh_selection_mode = 'EDGE'
|
||||
# value is [edge, face_sel_user_in]
|
||||
edge_dict= dict((ed.key(), [ed, 0]) for ed in me.edges)
|
||||
edge_dict= dict((ed.key, [ed, 0]) for ed in me.edges)
|
||||
|
||||
for f in me.faces:
|
||||
if f.selected:
|
||||
for edkey in f.edge_keys():
|
||||
for edkey in f.edge_keys:
|
||||
edge_dict[edkey][1] += 1
|
||||
|
||||
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
|
||||
@ -280,7 +280,7 @@ def getVertLoops(selEdges, me):
|
||||
vert_used = [False] * tot
|
||||
|
||||
for ed in selEdges:
|
||||
i1, i2 = ed.key()
|
||||
i1, i2 = ed.key
|
||||
vert_siblings[i1].append(i2)
|
||||
vert_siblings[i2].append(i1)
|
||||
|
||||
|
@ -176,35 +176,35 @@ static void bpy_init_modules( void )
|
||||
{
|
||||
PyObject *mod;
|
||||
|
||||
/* Needs to be first since this dir is needed for future modules */
|
||||
char *modpath= BLI_gethome_folder("scripts/modules", BLI_GETHOME_ALL);
|
||||
if(modpath) {
|
||||
PyObject *sys_path= PySys_GetObject("path"); /* borrow */
|
||||
PyObject *py_modpath= PyUnicode_FromString(modpath);
|
||||
PyList_Insert(sys_path, 0, py_modpath); /* add first */
|
||||
Py_DECREF(py_modpath);
|
||||
}
|
||||
|
||||
mod = PyModule_New("bpy");
|
||||
|
||||
PyModule_AddObject( mod, "data", BPY_rna_module() );
|
||||
/* PyModule_AddObject( mod, "doc", BPY_rna_doc() ); */
|
||||
PyModule_AddObject( mod, "types", BPY_rna_types() );
|
||||
PyModule_AddObject( mod, "props", BPY_rna_props() );
|
||||
PyModule_AddObject( mod, "__ops__", BPY_operator_module() ); /* ops is now a python module that does the conversion from SOME_OT_foo -> some.foo */
|
||||
PyModule_AddObject( mod, "ui", BPY_ui_module() ); // XXX very experimental, consider this a test, especially PyCObject is not meant to be permanent
|
||||
|
||||
|
||||
/* add the module so we can import it */
|
||||
PyDict_SetItemString(PySys_GetObject("modules"), "bpy", mod);
|
||||
Py_DECREF(mod);
|
||||
|
||||
/* add our own modules dir */
|
||||
{
|
||||
char *modpath= BLI_gethome_folder("scripts/modules", BLI_GETHOME_ALL);
|
||||
|
||||
if(modpath) {
|
||||
PyObject *sys_path= PySys_GetObject("path"); /* borrow */
|
||||
PyObject *py_modpath= PyUnicode_FromString(modpath);
|
||||
PyList_Insert(sys_path, 0, py_modpath); /* add first */
|
||||
Py_DECREF(py_modpath);
|
||||
}
|
||||
|
||||
bpy_import_test("bpy_ops"); /* adds its self to bpy.ops */
|
||||
bpy_import_test("bpy_utils"); /* adds its self to bpy.sys */
|
||||
bpy_import_test("bpy_ext"); /* extensions to our existing types */
|
||||
}
|
||||
|
||||
/* run first, initializes rna types */
|
||||
BPY_rna_init();
|
||||
|
||||
PyModule_AddObject( mod, "types", BPY_rna_types() ); /* needs to be first so bpy_types can run */
|
||||
bpy_import_test("bpy_types");
|
||||
PyModule_AddObject( mod, "data", BPY_rna_module() ); /* imports bpy_types by running this */
|
||||
bpy_import_test("bpy_types");
|
||||
/* PyModule_AddObject( mod, "doc", BPY_rna_doc() ); */
|
||||
PyModule_AddObject( mod, "props", BPY_rna_props() );
|
||||
PyModule_AddObject( mod, "__ops__", BPY_operator_module() ); /* ops is now a python module that does the conversion from SOME_OT_foo -> some.foo */
|
||||
PyModule_AddObject( mod, "ui", BPY_ui_module() ); // XXX very experimental, consider this a test, especially PyCObject is not meant to be permanent
|
||||
|
||||
|
||||
|
||||
/* bpy context */
|
||||
{
|
||||
bpy_context_module= ( BPy_StructRNA * ) PyObject_NEW( BPy_StructRNA, &pyrna_struct_Type );
|
||||
@ -213,11 +213,16 @@ static void bpy_init_modules( void )
|
||||
PyModule_AddObject(mod, "context", (PyObject *)bpy_context_module);
|
||||
}
|
||||
|
||||
|
||||
/* stand alone utility modules not related to blender directly */
|
||||
Geometry_Init();
|
||||
Mathutils_Init();
|
||||
BGL_Init();
|
||||
|
||||
/* add our own modules dir */
|
||||
{
|
||||
bpy_import_test("bpy_ops"); /* adds its self to bpy.ops */
|
||||
bpy_import_test("bpy_utils"); /* adds its self to bpy.sys */
|
||||
}
|
||||
}
|
||||
|
||||
void BPY_update_modules( void )
|
||||
|
@ -1364,16 +1364,6 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
|
||||
PropertyRNA *prop;
|
||||
FunctionRNA *func;
|
||||
|
||||
/* Include this incase this instance is a subtype of a python class
|
||||
* In these instances we may want to return a function or variable provided by the subtype
|
||||
*
|
||||
* Also needed to return methods when its not a subtype
|
||||
* */
|
||||
ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
|
||||
if (ret) return ret;
|
||||
else PyErr_Clear();
|
||||
/* done with subtypes */
|
||||
|
||||
if ((prop = RNA_struct_find_property(&self->ptr, name))) {
|
||||
ret = pyrna_prop_to_py(&self->ptr, prop);
|
||||
}
|
||||
@ -1419,8 +1409,18 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
|
||||
}
|
||||
}
|
||||
else {
|
||||
#if 0
|
||||
PyErr_Format( PyExc_AttributeError, "StructRNA - Attribute \"%.200s\" not found", name);
|
||||
ret = NULL;
|
||||
#endif
|
||||
/* Include this incase this instance is a subtype of a python class
|
||||
* In these instances we may want to return a function or variable provided by the subtype
|
||||
*
|
||||
* Also needed to return methods when its not a subtype
|
||||
* */
|
||||
|
||||
/* The error raised here will be displayed */
|
||||
ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -2551,6 +2551,7 @@ static PyObject* pyrna_srna_PyBase(StructRNA *srna) //, PyObject *bpy_types_dict
|
||||
|
||||
/* get the base type */
|
||||
base= RNA_struct_base(srna);
|
||||
|
||||
if(base && base != srna) {
|
||||
/*/printf("debug subtype %s %p\n", RNA_struct_identifier(srna), srna); */
|
||||
py_base= pyrna_srna_Subtype(base); //, bpy_types_dict);
|
||||
@ -2564,6 +2565,56 @@ static PyObject* pyrna_srna_PyBase(StructRNA *srna) //, PyObject *bpy_types_dict
|
||||
return py_base;
|
||||
}
|
||||
|
||||
/* check if we have a native python subclass, use it when it exists
|
||||
* return a borrowed reference */
|
||||
static PyObject* pyrna_srna_ExternalType(StructRNA *srna)
|
||||
{
|
||||
PyObject *bpy_types_dict= NULL;
|
||||
const char *idname= RNA_struct_identifier(srna);
|
||||
PyObject *newclass;
|
||||
|
||||
if(bpy_types_dict==NULL) {
|
||||
PyObject *bpy_types= PyImport_ImportModuleLevel("bpy_types", NULL, NULL, NULL, 0);
|
||||
|
||||
if(bpy_types==NULL) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
fprintf(stderr, "pyrna_srna_ExternalType: failed to find 'bpy_types' module\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bpy_types_dict = PyModule_GetDict(bpy_types); // borrow
|
||||
Py_DECREF(bpy_types); // fairly safe to assume the dict is kept
|
||||
}
|
||||
|
||||
newclass= PyDict_GetItemString(bpy_types_dict, idname);
|
||||
|
||||
/* sanity check, could skip this unless in debug mode */
|
||||
if(newclass) {
|
||||
PyObject *base_compare= pyrna_srna_PyBase(srna);
|
||||
PyObject *bases= PyObject_GetAttrString(newclass, "__bases__");
|
||||
|
||||
if(PyTuple_GET_SIZE(bases)) {
|
||||
PyObject *base= PyTuple_GET_ITEM(bases, 0);
|
||||
|
||||
if(base_compare != base) {
|
||||
PyLineSpit();
|
||||
fprintf(stderr, "pyrna_srna_ExternalType: incorrect subclassing of SRNA '%s'\n", idname);
|
||||
PyObSpit("Expected! ", base_compare);
|
||||
newclass= NULL;
|
||||
}
|
||||
else {
|
||||
if(G.f & G_DEBUG)
|
||||
fprintf(stderr, "SRNA Subclassed: '%s'\n", idname);
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(bases);
|
||||
}
|
||||
|
||||
return newclass;
|
||||
}
|
||||
|
||||
static PyObject* pyrna_srna_Subtype(StructRNA *srna)
|
||||
{
|
||||
PyObject *newclass = NULL;
|
||||
@ -2572,6 +2623,9 @@ static PyObject* pyrna_srna_Subtype(StructRNA *srna)
|
||||
newclass= NULL; /* Nothing to do */
|
||||
} else if ((newclass= RNA_struct_py_type_get(srna))) {
|
||||
Py_INCREF(newclass);
|
||||
} else if ((newclass= pyrna_srna_ExternalType(srna))) {
|
||||
pyrna_subtype_set_rna(newclass, srna);
|
||||
Py_INCREF(newclass);
|
||||
} else {
|
||||
/* subclass equivelents
|
||||
- class myClass(myBase):
|
||||
@ -2681,23 +2735,26 @@ PyObject *pyrna_prop_CreatePyObject( PointerRNA *ptr, PropertyRNA *prop )
|
||||
return ( PyObject * ) pyrna;
|
||||
}
|
||||
|
||||
/* bpy.data from python */
|
||||
static PointerRNA *rna_module_ptr= NULL;
|
||||
PyObject *BPY_rna_module( void )
|
||||
void BPY_rna_init(void)
|
||||
{
|
||||
BPy_StructRNA *pyrna;
|
||||
PointerRNA ptr;
|
||||
|
||||
#ifdef USE_MATHUTILS // register mathutils callbacks, ok to run more then once.
|
||||
mathutils_rna_array_cb_index= Mathutils_RegisterCallback(&mathutils_rna_array_cb);
|
||||
mathutils_rna_matrix_cb_index= Mathutils_RegisterCallback(&mathutils_rna_matrix_cb);
|
||||
#endif
|
||||
|
||||
|
||||
if( PyType_Ready( &pyrna_struct_Type ) < 0 )
|
||||
return NULL;
|
||||
|
||||
return;
|
||||
|
||||
if( PyType_Ready( &pyrna_prop_Type ) < 0 )
|
||||
return NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* bpy.data from python */
|
||||
static PointerRNA *rna_module_ptr= NULL;
|
||||
PyObject *BPY_rna_module(void)
|
||||
{
|
||||
BPy_StructRNA *pyrna;
|
||||
PointerRNA ptr;
|
||||
|
||||
/* for now, return the base RNA type rather then a real module */
|
||||
RNA_main_pointer_create(G.main, &ptr);
|
||||
@ -2735,21 +2792,22 @@ static PyObject *pyrna_basetype_getattro( BPy_BaseTypeRNA * self, PyObject *pyna
|
||||
PointerRNA newptr;
|
||||
PyObject *ret;
|
||||
|
||||
ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
|
||||
if (ret) return ret;
|
||||
else PyErr_Clear();
|
||||
|
||||
if (RNA_property_collection_lookup_string(&self->ptr, self->prop, _PyUnicode_AsString(pyname), &newptr)) {
|
||||
ret= pyrna_struct_Subtype(&newptr);
|
||||
if (ret==NULL) {
|
||||
PyErr_Format(PyExc_SystemError, "bpy.types.%.200s subtype could not be generated, this is a bug!", _PyUnicode_AsString(pyname));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else { /* Override the error */
|
||||
else {
|
||||
#if 0
|
||||
PyErr_Format(PyExc_AttributeError, "bpy.types.%.200s RNA_Struct does not exist", _PyUnicode_AsString(pyname));
|
||||
return NULL;
|
||||
#endif
|
||||
/* The error raised here will be displayed */
|
||||
ret= PyObject_GenericGetAttr((PyObject *)self, pyname);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *pyrna_basetype_dir(BPy_BaseTypeRNA *self);
|
||||
|
@ -64,6 +64,7 @@ typedef struct {
|
||||
/* cheap trick */
|
||||
#define BPy_BaseTypeRNA BPy_PropertyRNA
|
||||
|
||||
void BPY_rna_init( void );
|
||||
PyObject *BPY_rna_module( void );
|
||||
void BPY_update_rna_module( void );
|
||||
/*PyObject *BPY_rna_doc( void );*/
|
||||
|
Loading…
Reference in New Issue
Block a user