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