pep8 compliance for bpy_ops.py
[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
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 '__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     __idname__ = "mesh.delete_edgeloop"
177     __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(attr="path", name="Context Attributes",
186         description="rna context string", maxlen=1024, default="")
187
188 rna_reverse_prop = BoolProperty(attr="reverse", 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     __idname__ = "wm.context_set_boolean"
222     __label__ = "Context Set"
223     __props__ = [
224         rna_path_prop,
225         BoolProperty(attr="value", 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     __idname__ = "wm.context_set_int"
234     __label__ = "Context Set"
235     __props__ = [
236         rna_path_prop,
237             IntProperty(attr="value", name="Value",
238                 description="Assignment value", default=0)]
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     __idname__ = "wm.context_set_int"
245     __label__ = "Context Set"
246     __props__ = [
247         rna_path_prop,
248         FloatProperty(attr="value", name="Value",
249             description="Assignment value", default=0.0)]
250     execute = execute_context_assign
251
252
253 class WM_OT_context_set_string(bpy.types.Operator): # same as enum
254     '''Set a context value.'''
255     __idname__ = "wm.context_set_string"
256     __label__ = "Context Set"
257     __props__ = [
258         rna_path_prop,
259         StringProperty(attr="value", name="Value",
260             description="Assignment value", maxlen=1024, default="")]
261
262     execute = execute_context_assign
263
264
265 class WM_OT_context_set_enum(bpy.types.Operator):
266     '''Set a context value.'''
267     __idname__ = "wm.context_set_enum"
268     __label__ = "Context Set"
269     __props__ = [
270         rna_path_prop,
271         StringProperty(attr="value", name="Value",
272             description="Assignment value (as a string)",
273             maxlen=1024, default="")]
274
275     execute = execute_context_assign
276
277
278 class WM_OT_context_toggle(bpy.types.Operator):
279     '''Toggle a context value.'''
280     __idname__ = "wm.context_toggle"
281     __label__ = "Context Toggle"
282     __props__ = [rna_path_prop]
283
284     def execute(self, context):
285
286         if context_path_validate(context, self.path) == NullPathMember:
287             return ('PASS_THROUGH',)
288
289         exec("context.%s=not (context.%s)" % (self.path, self.path))
290         return ('FINISHED',)
291
292
293 class WM_OT_context_toggle_enum(bpy.types.Operator):
294     '''Toggle a context value.'''
295     __idname__ = "wm.context_toggle_enum"
296     __label__ = "Context Toggle Values"
297     __props__ = [
298         rna_path_prop,
299         StringProperty(attr="value_1", name="Value", \
300                 description="Toggle enum", maxlen=1024, default=""),
301
302         StringProperty(attr="value_2", name="Value", \
303                 description="Toggle enum", maxlen=1024, default="")]
304
305     def execute(self, context):
306
307         if context_path_validate(context, self.path) == NullPathMember:
308             return ('PASS_THROUGH',)
309
310         exec("context.%s = ['%s', '%s'][context.%s!='%s']" % \
311             (self.path, self.value_1, self.value_2, self.path, self.value_2))
312
313         return ('FINISHED',)
314
315
316 class WM_OT_context_cycle_int(bpy.types.Operator):
317     '''Set a context value. Useful for cycling active material,
318     vertex keys, groups' etc.'''
319     __idname__ = "wm.context_cycle_int"
320     __label__ = "Context Int Cycle"
321     __props__ = [rna_path_prop, rna_reverse_prop]
322
323     def execute(self, context):
324
325         value = context_path_validate(context, self.path)
326         if value == NullPathMember:
327             return ('PASS_THROUGH',)
328
329         self.value = value
330         if self.reverse:
331             self.value -= 1
332         else:
333             self.value += 1
334         execute_context_assign(self, context)
335
336         if self.value != eval("context.%s" % self.path):
337             # relies on rna clamping int's out of the range
338             if self.reverse:
339                 self.value = (1 << 32)
340             else:
341                 self.value = - (1 << 32)
342             execute_context_assign(self, context)
343
344         return ('FINISHED',)
345
346
347 class WM_OT_context_cycle_enum(bpy.types.Operator):
348     '''Toggle a context value.'''
349     __idname__ = "wm.context_cycle_enum"
350     __label__ = "Context Enum Cycle"
351     __props__ = [rna_path_prop, rna_reverse_prop]
352
353     def execute(self, context):
354
355         value = context_path_validate(context, self.path)
356         if value == NullPathMember:
357             return ('PASS_THROUGH',)
358
359         orig_value = value
360
361         # Have to get rna enum values
362         rna_struct_str, rna_prop_str = self.path.rsplit('.', 1)
363         i = rna_prop_str.find('[')
364
365         # just incse we get "context.foo.bar[0]"
366         if i != -1:
367             rna_prop_str = rna_prop_str[0:i]
368
369         rna_struct = eval("context.%s.rna_type" % rna_struct_str)
370
371         rna_prop = rna_struct.properties[rna_prop_str]
372
373         if type(rna_prop) != bpy.types.EnumProperty:
374             raise Exception("expected an enum property")
375
376         enums = rna_struct.properties[rna_prop_str].items.keys()
377         orig_index = enums.index(orig_value)
378
379         # Have the info we need, advance to the next item
380         if self.reverse:
381             if orig_index == 0:
382                 advance_enum = enums[-1]
383             else:
384                 advance_enum = enums[orig_index-1]
385         else:
386             if orig_index == len(enums) - 1:
387                 advance_enum = enums[0]
388             else:
389                 advance_enum = enums[orig_index + 1]
390
391         # set the new value
392         exec("context.%s=advance_enum" % self.path)
393         return ('FINISHED',)
394
395 doc_id = StringProperty(attr="doc_id", name="Doc ID",
396         description="ID for the documentation", maxlen=1024, default="")
397
398 doc_new = StringProperty(attr="doc_new", name="Doc New",
399         description="", maxlen=1024, default="")
400
401
402 class WM_OT_doc_view(bpy.types.Operator):
403     '''Load online reference docs'''
404     __idname__ = "wm.doc_view"
405     __label__ = "View Documentation"
406     __props__ = [doc_id]
407     _prefix = 'http://www.blender.org/documentation/250PythonDoc'
408
409     def _nested_class_string(self, class_string):
410         ls = []
411         class_obj = getattr(bpy.types, class_string, None).__rna__
412         while class_obj:
413             ls.insert(0, class_obj)
414             class_obj = class_obj.nested
415         return '.'.join([class_obj.identifier for class_obj in ls])
416
417     def execute(self, context):
418         id_split = self.doc_id.split('.')
419         if len(id_split) == 1: # rna, class
420             url = '%s/bpy.types.%s-class.html' % (self._prefix, id_split[0])
421         elif len(id_split) == 2: # rna, class.prop
422             class_name, class_prop = id_split
423
424             # It so happens that epydoc nests these
425             class_name_full = self._nested_class_string(class_name)
426
427             if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
428                 url = '%s/bpy.ops.%s-module.html#%s' % \
429                         (self._prefix, class_name_full, class_prop)
430             else:
431                 url = '%s/bpy.types.%s-class.html#%s' % \
432                         (self._prefix, class_name_full, class_prop)
433
434         else:
435             return ('PASS_THROUGH',)
436
437         import webbrowser
438         webbrowser.open(url)
439
440         return ('FINISHED',)
441
442
443 class WM_OT_doc_edit(bpy.types.Operator):
444     '''Load online reference docs'''
445     __idname__ = "wm.doc_edit"
446     __label__ = "Edit Documentation"
447     __props__ = [doc_id, doc_new]
448     _url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
449
450     def _send_xmlrpc(self, data_dict):
451         print("sending data:", data_dict)
452
453         import xmlrpc.client
454         user = 'blenderuser'
455         pwd = 'blender>user'
456
457         docblog = xmlrpc.client.ServerProxy(self._url)
458         docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
459
460     def execute(self, context):
461
462         class_name, class_prop = self.doc_id.split('.')
463
464         if not self.doc_new:
465             return 'OPERATOR_CANCELLED'
466
467         # check if this is an operator
468         op_name = class_name.upper() + '_OT_' + class_prop
469         op_class = getattr(bpy.types, op_name, None)
470
471         # Upload this to the web server
472         upload = {}
473
474         if op_class:
475             rna = op_class.__rna__
476             doc_orig = rna.description
477             if doc_orig == self.doc_new:
478                 return 'OPERATOR_CANCELLED'
479
480             print("op - old:'%s' -> new:'%s'" % (doc_orig, self.doc_new))
481             upload["title"] = 'OPERATOR %s:%s' % (self.doc_id, doc_orig)
482             upload["description"] = self.doc_new
483
484             self._send_xmlrpc(upload)
485
486         else:
487             rna = getattr(bpy.types, class_name).__rna__
488             doc_orig = rna.properties[class_prop].description
489             if doc_orig == self.doc_new:
490                 return 'OPERATOR_CANCELLED'
491
492             print("rna - old:'%s' -> new:'%s'" % (doc_orig, self.doc_new))
493             upload["title"] = 'RNA %s:%s' % s(self.doc_id, doc_orig)
494
495         upload["description"] = self.doc_new
496
497         self._send_xmlrpc(upload)
498
499         return ('FINISHED',)
500
501     def invoke(self, context, event):
502         wm = context.manager
503         wm.invoke_props_popup(self.__operator__, event)
504         return ('RUNNING_MODAL',)
505
506
507 bpy.ops.add(MESH_OT_delete_edgeloop)
508
509 bpy.ops.add(WM_OT_context_set_boolean)
510 bpy.ops.add(WM_OT_context_set_int)
511 bpy.ops.add(WM_OT_context_set_float)
512 bpy.ops.add(WM_OT_context_set_string)
513 bpy.ops.add(WM_OT_context_set_enum)
514 bpy.ops.add(WM_OT_context_toggle)
515 bpy.ops.add(WM_OT_context_toggle_enum)
516 bpy.ops.add(WM_OT_context_cycle_enum)
517 bpy.ops.add(WM_OT_context_cycle_int)
518
519 bpy.ops.add(WM_OT_doc_view)
520 bpy.ops.add(WM_OT_doc_edit)