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