fbb4a8b537d51329da85ce746cf95cb1d35518b7
[blender.git] / release / scripts / modules / bpy_ops.py
1 # for slightly faster access
2 from bpy.__ops__ import add             as op_add
3 from bpy.__ops__ import remove          as op_remove
4 from bpy.__ops__ import dir             as op_dir
5 from bpy.__ops__ import call            as op_call
6 from bpy.__ops__ import as_string       as op_as_string
7 from bpy.__ops__ import get_rna as op_get_rna
8
9 # Keep in sync with WM_types.h
10 context_dict = {
11         'INVOKE_DEFAULT':0,
12         'INVOKE_REGION_WIN':1,
13         'INVOKE_AREA':2,
14         'INVOKE_SCREEN':3,
15         'EXEC_DEFAULT':4,
16         'EXEC_REGION_WIN':5,
17         'EXEC_AREA':6,
18         'EXEC_SCREEN':7,
19 }
20
21 class bpy_ops(object):
22         '''
23         Fake module like class.
24
25          bpy.ops
26         '''
27
28         def __getattr__(self, module):
29                 '''
30                 gets a bpy.ops submodule
31                 '''
32                 if module.startswith('__'):
33                         raise AttributeError(module)
34                 return bpy_ops_submodule(module)
35                 
36         def add(self, pyop):
37                 op_add(pyop)
38         
39         def remove(self, pyop):
40                 op_remove(pyop)
41         
42         def __dir__(self):
43                 
44                 submodules = set()
45                 
46                 # add this classes functions
47                 for id_name in dir(self.__class__):
48                         if not id_name.startswith('__'):
49                                 submodules.add(id_name)
50                 
51                 for id_name in op_dir():
52                         id_split = id_name.split('_OT_', 1)
53                         
54                         if len(id_split) == 2:
55                                 submodules.add(id_split[0].lower())
56                         else:
57                                 submodules.add(id_split[0])
58                 
59                 return list(submodules)
60                 
61         def __repr__(self):
62                 return "<module like class 'bpy.ops'>"
63
64
65 class bpy_ops_submodule(object):
66         '''
67         Utility class to fake submodules.
68         
69         eg. bpy.ops.object
70         '''
71         __keys__ = ('module',)
72         
73         def __init__(self, module):
74                 self.module = module
75                 
76         def __getattr__(self, func):
77                 '''
78                 gets a bpy.ops.submodule function
79                 '''
80                 return bpy_ops_submodule_op(self.module, func)
81                 
82         def __dir__(self):
83                 
84                 functions = set()
85                 
86                 module_upper = self.module.upper()
87                 
88                 for id_name in op_dir():
89                         id_split = id_name.split('_OT_', 1)
90                         if len(id_split) == 2 and module_upper == id_split[0]:
91                                 functions.add(id_split[1])
92                 
93                 return list(functions)
94         
95         def __repr__(self):
96                 return "<module like class 'bpy.ops.%s'>" % self.module
97
98 class bpy_ops_submodule_op(object):
99         '''
100         Utility class to fake submodule operators.
101         
102         eg. bpy.ops.object.somefunc
103         '''
104         __keys__ = ('module', 'func')
105         def __init__(self, module, func):
106                 self.module = module
107                 self.func = func
108         
109         def idname(self):
110                 # submod.foo -> SUBMOD_OT_foo
111                 return self.module.upper() + '_OT_' + self.func
112         
113         def __call__(self, *args, **kw):
114                 
115                 # Get the operator from blender
116                 if len(args) > 2:
117                         raise ValueError("only 1 or 2 arguments for the execution context is supported")
118                 
119                 C_dict = None
120                 
121                 if args:
122                         
123                         C_exec = 'EXEC_DEFAULT'
124                         
125                         if len(args) == 2:
126                                 C_exec = args[0]
127                                 C_dict = args[1]
128                         else:
129                                 if type(args[0]) != str:
130                                         C_dict= args[0]
131                                 else:
132                                         C_exec= args[0]
133                         
134                         try:
135                                 context = context_dict[C_exec]
136                         except:
137                                 raise ValueError("Expected a single context argument in: " + str(list(context_dict.keys())))
138                         
139                         if len(args) == 2:
140                                 C_dict= args[1]
141                         
142                         return op_call(self.idname() , C_dict, kw, context)
143                 
144                 else:
145                         return op_call(self.idname(), C_dict, kw)
146         
147         def get_rna(self):
148                 '''
149                 currently only used for '__rna__'
150                 '''
151                 return op_get_rna(self.idname())
152                         
153         
154         def __repr__(self): # useful display, repr(op)
155                 return op_as_string(self.idname())
156         
157         def __str__(self): # used for print(...)
158                 return "<function bpy.ops.%s.%s at 0x%x'>" % (self.module, self.func, id(self))
159
160 import bpy
161 bpy.ops = bpy_ops()
162
163 # TODO, C macro's cant define settings :|
164
165 class MESH_OT_delete_edgeloop(bpy.types.Operator):
166         '''Export a single object as a stanford PLY with normals, colours and texture coordinates.'''
167         __idname__ = "mesh.delete_edgeloop"
168         __label__ = "Delete Edge Loop"
169         
170         def execute(self, context):
171                 bpy.ops.tfm.edge_slide(value=1.0)
172                 bpy.ops.mesh.select_more()
173                 bpy.ops.mesh.remove_doubles()
174                 return ('FINISHED',)
175
176 rna_path_prop = bpy.props.StringProperty(attr="path", name="Context Attributes", description="rna context string", maxlen= 1024, default= "")
177 rna_reverse_prop = bpy.props.BoolProperty(attr="reverse", name="Reverse", description="Cycle backwards", default= False)
178
179 class NullPathMember:
180         pass
181
182 def context_path_validate(context, path):
183         import sys
184         try:
185                 value = eval("context.%s" % path)
186         except AttributeError:
187                 if "'NoneType'" in str(sys.exc_info()[1]):
188                         # One of the items in the rna path is None, just ignore this
189                         value = NullPathMember
190                 else:
191                         # We have a real error in the rna path, dont ignore that
192                         raise
193         
194         return value
195                 
196         
197
198 def execute_context_assign(self, context):
199         if context_path_validate(context, self.path) == NullPathMember:
200                 return ('PASS_THROUGH',)
201         
202         exec("context.%s=self.value" % self.path)
203         return ('FINISHED',)
204
205 class WM_OT_context_set_boolean(bpy.types.Operator):
206         '''Set a context value.'''
207         __idname__ = "wm.context_set_boolean"
208         __label__ = "Context Set"
209         __props__ = [rna_path_prop, bpy.props.BoolProperty(attr="value", name="Value", description="Assignment value", default= True)]
210         execute = execute_context_assign
211
212 class WM_OT_context_set_int(bpy.types.Operator): # same as enum
213         '''Set a context value.'''
214         __idname__ = "wm.context_set_int"
215         __label__ = "Context Set"
216         __props__ = [rna_path_prop, bpy.props.IntProperty(attr="value", name="Value", description="Assignment value", default= 0)]
217         execute = execute_context_assign
218                 
219 class WM_OT_context_set_float(bpy.types.Operator): # same as enum
220         '''Set a context value.'''
221         __idname__ = "wm.context_set_int"
222         __label__ = "Context Set"
223         __props__ = [rna_path_prop, bpy.props.FloatProperty(attr="value", name="Value", description="Assignment value", default= 0.0)]
224         execute = execute_context_assign
225
226 class WM_OT_context_set_string(bpy.types.Operator): # same as enum
227         '''Set a context value.'''
228         __idname__ = "wm.context_set_string"
229         __label__ = "Context Set"
230         __props__ = [rna_path_prop, bpy.props.StringProperty(attr="value", name="Value", description="Assignment value", maxlen= 1024, default= "")]
231         execute = execute_context_assign
232
233 class WM_OT_context_set_enum(bpy.types.Operator):
234         '''Set a context value.'''
235         __idname__ = "wm.context_set_enum"
236         __label__ = "Context Set"
237         __props__ = [rna_path_prop, bpy.props.StringProperty(attr="value", name="Value", description="Assignment value (as a string)", maxlen= 1024, default= "")]
238         execute = execute_context_assign
239
240 class WM_OT_context_toggle(bpy.types.Operator):
241         '''Toggle a context value.'''
242         __idname__ = "wm.context_toggle"
243         __label__ = "Context Toggle"
244         __props__ = [rna_path_prop]
245         def execute(self, context):
246                 
247                 if context_path_validate(context, self.path) == NullPathMember:
248                         return ('PASS_THROUGH',)
249                 
250                 exec("context.%s=not (context.%s)" % (self.path, self.path)) # security nuts will complain.
251                 return ('FINISHED',)
252
253 class WM_OT_context_toggle_enum(bpy.types.Operator):
254         '''Toggle a context value.'''
255         __idname__ = "wm.context_toggle_enum"
256         __label__ = "Context Toggle Values"
257         __props__ = [
258                 rna_path_prop,
259                 bpy.props.StringProperty(attr="value_1", name="Value", description="Toggle enum", maxlen= 1024, default= ""),
260                 bpy.props.StringProperty(attr="value_2", name="Value", description="Toggle enum", maxlen= 1024, default= "")
261         ]
262         def execute(self, context):
263                 
264                 if context_path_validate(context, self.path) == NullPathMember:
265                         return ('PASS_THROUGH',)
266                 
267                 exec("context.%s = ['%s', '%s'][context.%s!='%s']" % (self.path, self.value_1, self.value_2, self.path, self.value_2)) # security nuts will complain.
268                 return ('FINISHED',)
269
270 class WM_OT_context_cycle_int(bpy.types.Operator):
271         '''Set a context value. Useful for cycling active material, vertex keys, groups' etc.'''
272         __idname__ = "wm.context_cycle_int"
273         __label__ = "Context Int Cycle"
274         __props__ = [rna_path_prop, rna_reverse_prop]
275         def execute(self, context):
276                 
277                 value = context_path_validate(context, self.path)
278                 if value == NullPathMember:
279                         return ('PASS_THROUGH',)
280                 
281                 self.value = value
282                 if self.reverse:        self.value -= 1
283                 else:           self.value += 1
284                 execute_context_assign(self, context)
285                 
286                 if self.value != eval("context.%s" % self.path):
287                         # relies on rna clamping int's out of the range
288                         if self.reverse:        self.value =  (1<<32)
289                         else:           self.value = -(1<<32)
290                         execute_context_assign(self, context)
291                         
292                 return ('FINISHED',)
293
294 class WM_OT_context_cycle_enum(bpy.types.Operator):
295         '''Toggle a context value.'''
296         __idname__ = "wm.context_cycle_enum"
297         __label__ = "Context Enum Cycle"
298         __props__ = [rna_path_prop, rna_reverse_prop]
299         def execute(self, context):
300                 
301                 value = context_path_validate(context, self.path)
302                 if value == NullPathMember:
303                         return ('PASS_THROUGH',)
304                 
305                 orig_value = value
306                 
307                 # Have to get rna enum values
308                 rna_struct_str, rna_prop_str =  self.path.rsplit('.', 1)
309                 i = rna_prop_str.find('[')
310                 if i != -1: rna_prop_str = rna_prop_str[0:i] # just incse we get "context.foo.bar[0]"
311                 
312                 rna_struct = eval("context.%s.rna_type" % rna_struct_str)
313                 
314                 rna_prop = rna_struct.properties[rna_prop_str]
315                 
316                 if type(rna_prop) != bpy.types.EnumProperty:
317                         raise Exception("expected an enum property")
318                 
319                 enums = rna_struct.properties[rna_prop_str].items.keys()
320                 orig_index = enums.index(orig_value)
321                 
322                 # Have the info we need, advance to the next item
323                 if self.reverse:
324                         if orig_index==0:                       advance_enum = enums[-1]
325                         else:                                   advance_enum = enums[orig_index-1]
326                 else:
327                         if orig_index==len(enums)-1:    advance_enum = enums[0]
328                         else:                                   advance_enum = enums[orig_index+1]
329                 
330                 # set the new value
331                 exec("context.%s=advance_enum" % self.path)
332                 return ('FINISHED',)
333
334 doc_id = bpy.props.StringProperty(attr="doc_id", name="Doc ID", description="ID for the documentation", maxlen= 1024, default= "")
335 doc_new = bpy.props.StringProperty(attr="doc_new", name="Doc New", description="", maxlen= 1024, default= "")
336
337
338 class WM_OT_doc_view(bpy.types.Operator):
339         '''Load online reference docs'''
340         __idname__ = "wm.doc_view"
341         __label__ = "View Documentation"
342         __props__ = [doc_id]
343         _prefix = 'http://www.blender.org/documentation/250PythonDoc'
344         
345         def _nested_class_string(self, class_string):
346                 ls = []
347                 class_obj = getattr(bpy.types, class_string, None).__rna__
348                 while class_obj:
349                         ls.insert(0, class_obj)
350                         class_obj = class_obj.nested
351                 return '.'.join([class_obj.identifier for class_obj in ls])
352         
353         def execute(self, context):
354                 id_split = self.doc_id.split('.')
355                 # Example url, http://www.graphicall.org/ftp/ideasman42/html/bpy.types.Space3DView-class.html#background_image
356                 # Example operator http://www.graphicall.org/ftp/ideasman42/html/bpy.ops.boid-module.html#boidrule_add
357                 if len(id_split) == 1: # rna, class
358                         url= '%s/bpy.types.%s-class.html' % (self._prefix, id_split[0])
359                 elif len(id_split) == 2: # rna, class.prop
360                         class_name, class_prop = id_split
361                         
362                         class_name_full = self._nested_class_string(class_name) # It so happens that epydoc nests these
363                         
364                         if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
365                                 url= '%s/bpy.ops.%s-module.html#%s' % (self._prefix, class_name_full, class_prop)
366                         else:
367                                 url= '%s/bpy.types.%s-class.html#%s' % (self._prefix, class_name_full, class_prop)
368                         
369                 else:
370                         return ('PASS_THROUGH',)
371                 
372                 import webbrowser
373                 webbrowser.open(url)
374                 
375                 return ('FINISHED',)
376
377
378 class WM_OT_doc_edit(bpy.types.Operator):
379         '''Load online reference docs'''
380         __idname__ = "wm.doc_edit"
381         __label__ = "Edit Documentation"
382         __props__ = [doc_id, doc_new]
383         
384         
385         def _send_xmlrpc(self, data_dict):              
386                 print("sending data:", data_dict)
387                 
388                 import xmlrpc.client
389                 user = 'blenderuser'
390                 pwd = 'blender>user'
391                 
392                 docblog = xmlrpc.client.ServerProxy("http://www.mindrones.com/blender/svn/xmlrpc.php")
393                 docblog.metaWeblog.newPost(1,user,pwd, data_dict,1)
394         
395         def execute(self, context):
396                 
397                 class_name, class_prop = self.doc_id.split('.')
398                 
399                 if self.doc_new:
400                         op_class = getattr(bpy.types, class_name.upper() + '_OT_' + class_prop, None)
401                         
402                         if op_class:
403                                 doc_orig = op_class.__rna__.description
404                                 if doc_orig != self.doc_new:
405                                         print("operator - old:'%s' -> new:'%s'" % (doc_orig, self.doc_new))
406                                         self._send_xmlrpc({'title':'OPERATOR %s:%s' % (self.doc_id,doc_orig),'description':self.doc_new})
407                         else:
408                                 doc_orig = getattr(bpy.types, class_name).__rna__.properties[class_prop].description
409                                 if doc_orig != self.doc_new:
410                                         print("rna - old:'%s' -> new:'%s'" % (doc_orig, self.doc_new))
411                                         # Ugh, will run this on every edit.... better not make any mistakes
412                                         self._send_xmlrpc({'title':'RNA %s:%s' % (self.doc_id,doc_orig),'description':self.doc_new})
413                                         
414                 return ('FINISHED',)
415         
416         def invoke(self, context, event):
417                 wm = context.manager
418                 wm.invoke_props_popup(self.__operator__, event)
419                 return ('RUNNING_MODAL',)
420
421
422 bpy.ops.add(MESH_OT_delete_edgeloop)
423
424 bpy.ops.add(WM_OT_context_set_boolean)
425 bpy.ops.add(WM_OT_context_set_int)
426 bpy.ops.add(WM_OT_context_set_float)
427 bpy.ops.add(WM_OT_context_set_string)
428 bpy.ops.add(WM_OT_context_set_enum)
429 bpy.ops.add(WM_OT_context_toggle)
430 bpy.ops.add(WM_OT_context_toggle_enum)
431 bpy.ops.add(WM_OT_context_cycle_enum)
432 bpy.ops.add(WM_OT_context_cycle_int)
433
434 bpy.ops.add(WM_OT_doc_view)
435 bpy.ops.add(WM_OT_doc_edit)