A few small fixes to new UI messages...
[blender.git] / release / scripts / startup / bl_operators / wm.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22 from bpy.types import Menu, Operator
23 from bpy.props import (StringProperty,
24                        BoolProperty,
25                        IntProperty,
26                        FloatProperty,
27                        EnumProperty,
28                        )
29
30 from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear
31
32 import subprocess
33 import os
34
35 class MESH_OT_delete_edgeloop(Operator):
36     '''Delete an edge loop by merging the faces on each side to a single face loop'''
37     bl_idname = "mesh.delete_edgeloop"
38     bl_label = "Delete Edge Loop"
39
40     def execute(self, context):
41         if 'FINISHED' in bpy.ops.transform.edge_slide(value=1.0):
42             bpy.ops.mesh.select_more()
43             bpy.ops.mesh.remove_doubles()
44             return {'FINISHED'}
45
46         return {'CANCELLED'}
47
48 rna_path_prop = StringProperty(
49         name="Context Attributes",
50         description="rna context string",
51         maxlen=1024,
52         )
53
54 rna_reverse_prop = BoolProperty(
55         name="Reverse",
56         description="Cycle backwards",
57         default=False,
58         )
59
60 rna_relative_prop = BoolProperty(
61         name="Relative",
62         description="Apply relative to the current value (delta)",
63         default=False,
64         )
65
66
67 def context_path_validate(context, data_path):
68     try:
69         value = eval("context.%s" % data_path) if data_path else Ellipsis
70     except AttributeError as e:
71         if str(e).startswith("'NoneType'"):
72             # One of the items in the rna path is None, just ignore this
73             value = Ellipsis
74         else:
75             # We have a real error in the rna path, don't ignore that
76             raise
77
78     return value
79
80
81 def operator_value_is_undo(value):
82     if value in {None, Ellipsis}:
83         return False
84
85     # typical properties or objects
86     id_data = getattr(value, "id_data", Ellipsis)
87
88     if id_data is None:
89         return False
90     elif id_data is Ellipsis:
91         # handle mathutils types
92         id_data = getattr(getattr(value, "owner", None), "id_data", None)
93
94         if id_data is None:
95             return False
96
97     # return True if its a non window ID type
98     return (isinstance(id_data, bpy.types.ID) and
99             (not isinstance(id_data, (bpy.types.WindowManager,
100                                       bpy.types.Screen,
101                                       bpy.types.Scene,
102                                       bpy.types.Brush,
103                                       ))))
104
105
106 def operator_path_is_undo(context, data_path):
107     # note that if we have data paths that use strings this could fail
108     # luckily we don't do this!
109     #
110     # When we cant find the data owner assume no undo is needed.
111     data_path_head = data_path.rpartition(".")[0]
112
113     if not data_path_head:
114         return False
115
116     value = context_path_validate(context, data_path_head)
117
118     return operator_value_is_undo(value)
119
120
121 def operator_path_undo_return(context, data_path):
122     return {'FINISHED'} if operator_path_is_undo(context, data_path) else {'CANCELLED'}
123
124
125 def operator_value_undo_return(value):
126     return {'FINISHED'} if operator_value_is_undo(value) else {'CANCELLED'}
127
128
129 def execute_context_assign(self, context):
130     data_path = self.data_path
131     if context_path_validate(context, data_path) is Ellipsis:
132         return {'PASS_THROUGH'}
133
134     if getattr(self, "relative", False):
135         exec("context.%s += self.value" % data_path)
136     else:
137         exec("context.%s = self.value" % data_path)
138
139     return operator_path_undo_return(context, data_path)
140
141
142 class BRUSH_OT_active_index_set(Operator):
143     '''Set active sculpt/paint brush from it's number'''
144     bl_idname = "brush.active_index_set"
145     bl_label = "Set Brush Number"
146
147     mode = StringProperty(
148             name="mode",
149             description="Paint mode to set brush for",
150             maxlen=1024,
151             )
152     index = IntProperty(
153             name="number",
154             description="Brush number",
155             )
156
157     _attr_dict = {"sculpt": "use_paint_sculpt",
158                   "vertex_paint": "use_paint_vertex",
159                   "weight_paint": "use_paint_weight",
160                   "image_paint": "use_paint_image",
161                   }
162
163     def execute(self, context):
164         attr = self._attr_dict.get(self.mode)
165         if attr is None:
166             return {'CANCELLED'}
167
168         for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))):
169             if i == self.index:
170                 getattr(context.tool_settings, self.mode).brush = brush
171                 return {'FINISHED'}
172
173         return {'CANCELLED'}
174
175
176 class WM_OT_context_set_boolean(Operator):
177     '''Set a context value'''
178     bl_idname = "wm.context_set_boolean"
179     bl_label = "Context Set Boolean"
180     bl_options = {'UNDO', 'INTERNAL'}
181
182     data_path = rna_path_prop
183     value = BoolProperty(
184             name="Value",
185             description="Assignment value",
186             default=True,
187             )
188
189     execute = execute_context_assign
190
191
192 class WM_OT_context_set_int(Operator):  # same as enum
193     '''Set a context value'''
194     bl_idname = "wm.context_set_int"
195     bl_label = "Context Set"
196     bl_options = {'UNDO', 'INTERNAL'}
197
198     data_path = rna_path_prop
199     value = IntProperty(
200             name="Value",
201             description="Assign value",
202             default=0,
203             )
204     relative = rna_relative_prop
205
206     execute = execute_context_assign
207
208
209 class WM_OT_context_scale_int(Operator):
210     '''Scale an int context value'''
211     bl_idname = "wm.context_scale_int"
212     bl_label = "Context Set"
213     bl_options = {'UNDO', 'INTERNAL'}
214
215     data_path = rna_path_prop
216     value = FloatProperty(
217             name="Value",
218             description="Assign value",
219             default=1.0,
220             )
221     always_step = BoolProperty(
222             name="Always Step",
223             description="Always adjust the value by a minimum of 1 when 'value' is not 1.0",
224             default=True,
225             )
226
227     def execute(self, context):
228         data_path = self.data_path
229         if context_path_validate(context, data_path) is Ellipsis:
230             return {'PASS_THROUGH'}
231
232         value = self.value
233
234         if value == 1.0:  # nothing to do
235             return {'CANCELLED'}
236
237         if getattr(self, "always_step", False):
238             if value > 1.0:
239                 add = "1"
240                 func = "max"
241             else:
242                 add = "-1"
243                 func = "min"
244             exec("context.%s = %s(round(context.%s * value), context.%s + %s)" %
245                  (data_path, func, data_path, data_path, add))
246         else:
247             exec("context.%s *= value" % data_path)
248
249         return operator_path_undo_return(context, data_path)
250
251
252 class WM_OT_context_set_float(Operator):  # same as enum
253     '''Set a context value'''
254     bl_idname = "wm.context_set_float"
255     bl_label = "Context Set Float"
256     bl_options = {'UNDO', 'INTERNAL'}
257
258     data_path = rna_path_prop
259     value = FloatProperty(
260             name="Value",
261             description="Assignment value",
262             default=0.0,
263             )
264     relative = rna_relative_prop
265
266     execute = execute_context_assign
267
268
269 class WM_OT_context_set_string(Operator):  # same as enum
270     '''Set a context value'''
271     bl_idname = "wm.context_set_string"
272     bl_label = "Context Set String"
273     bl_options = {'UNDO', 'INTERNAL'}
274
275     data_path = rna_path_prop
276     value = StringProperty(
277             name="Value",
278             description="Assign value",
279             maxlen=1024,
280             )
281
282     execute = execute_context_assign
283
284
285 class WM_OT_context_set_enum(Operator):
286     '''Set a context value'''
287     bl_idname = "wm.context_set_enum"
288     bl_label = "Context Set Enum"
289     bl_options = {'UNDO', 'INTERNAL'}
290
291     data_path = rna_path_prop
292     value = StringProperty(
293             name="Value",
294             description="Assignment value (as a string)",
295             maxlen=1024,
296             )
297
298     execute = execute_context_assign
299
300
301 class WM_OT_context_set_value(Operator):
302     '''Set a context value'''
303     bl_idname = "wm.context_set_value"
304     bl_label = "Context Set Value"
305     bl_options = {'UNDO', 'INTERNAL'}
306
307     data_path = rna_path_prop
308     value = StringProperty(
309             name="Value",
310             description="Assignment value (as a string)",
311             maxlen=1024,
312             )
313
314     def execute(self, context):
315         data_path = self.data_path
316         if context_path_validate(context, data_path) is Ellipsis:
317             return {'PASS_THROUGH'}
318         exec("context.%s = %s" % (data_path, self.value))
319         return operator_path_undo_return(context, data_path)
320
321
322 class WM_OT_context_toggle(Operator):
323     '''Toggle a context value'''
324     bl_idname = "wm.context_toggle"
325     bl_label = "Context Toggle"
326     bl_options = {'UNDO', 'INTERNAL'}
327
328     data_path = rna_path_prop
329
330     def execute(self, context):
331         data_path = self.data_path
332
333         if context_path_validate(context, data_path) is Ellipsis:
334             return {'PASS_THROUGH'}
335
336         exec("context.%s = not (context.%s)" % (data_path, data_path))
337
338         return operator_path_undo_return(context, data_path)
339
340
341 class WM_OT_context_toggle_enum(Operator):
342     '''Toggle a context value'''
343     bl_idname = "wm.context_toggle_enum"
344     bl_label = "Context Toggle Values"
345     bl_options = {'UNDO', 'INTERNAL'}
346
347     data_path = rna_path_prop
348     value_1 = StringProperty(
349             name="Value",
350             description="Toggle enum",
351             maxlen=1024,
352             )
353     value_2 = StringProperty(
354             name="Value",
355             description="Toggle enum",
356             maxlen=1024,
357             )
358
359     def execute(self, context):
360         data_path = self.data_path
361
362         if context_path_validate(context, data_path) is Ellipsis:
363             return {'PASS_THROUGH'}
364
365         exec("context.%s = ('%s', '%s')[context.%s != '%s']" %
366              (data_path, self.value_1,
367               self.value_2, data_path,
368               self.value_2,
369               ))
370
371         return operator_path_undo_return(context, data_path)
372
373
374 class WM_OT_context_cycle_int(Operator):
375     '''Set a context value. Useful for cycling active material, '''
376     '''vertex keys, groups' etc'''
377     bl_idname = "wm.context_cycle_int"
378     bl_label = "Context Int Cycle"
379     bl_options = {'UNDO', 'INTERNAL'}
380
381     data_path = rna_path_prop
382     reverse = rna_reverse_prop
383
384     def execute(self, context):
385         data_path = self.data_path
386         value = context_path_validate(context, data_path)
387         if value is Ellipsis:
388             return {'PASS_THROUGH'}
389
390         if self.reverse:
391             value -= 1
392         else:
393             value += 1
394
395         exec("context.%s = value" % data_path)
396
397         if value != eval("context.%s" % data_path):
398             # relies on rna clamping int's out of the range
399             if self.reverse:
400                 value = (1 << 31) - 1
401             else:
402                 value = -1 << 31
403
404             exec("context.%s = value" % data_path)
405
406         return operator_path_undo_return(context, data_path)
407
408
409 class WM_OT_context_cycle_enum(Operator):
410     '''Toggle a context value'''
411     bl_idname = "wm.context_cycle_enum"
412     bl_label = "Context Enum Cycle"
413     bl_options = {'UNDO', 'INTERNAL'}
414
415     data_path = rna_path_prop
416     reverse = rna_reverse_prop
417
418     def execute(self, context):
419         data_path = self.data_path
420         value = context_path_validate(context, data_path)
421         if value is Ellipsis:
422             return {'PASS_THROUGH'}
423
424         orig_value = value
425
426         # Have to get rna enum values
427         rna_struct_str, rna_prop_str = data_path.rsplit('.', 1)
428         i = rna_prop_str.find('[')
429
430         # just in case we get "context.foo.bar[0]"
431         if i != -1:
432             rna_prop_str = rna_prop_str[0:i]
433
434         rna_struct = eval("context.%s.rna_type" % rna_struct_str)
435
436         rna_prop = rna_struct.properties[rna_prop_str]
437
438         if type(rna_prop) != bpy.types.EnumProperty:
439             raise Exception("expected an enum property")
440
441         enums = rna_struct.properties[rna_prop_str].enum_items.keys()
442         orig_index = enums.index(orig_value)
443
444         # Have the info we need, advance to the next item
445         if self.reverse:
446             if orig_index == 0:
447                 advance_enum = enums[-1]
448             else:
449                 advance_enum = enums[orig_index - 1]
450         else:
451             if orig_index == len(enums) - 1:
452                 advance_enum = enums[0]
453             else:
454                 advance_enum = enums[orig_index + 1]
455
456         # set the new value
457         exec("context.%s = advance_enum" % data_path)
458         return operator_path_undo_return(context, data_path)
459
460
461 class WM_OT_context_cycle_array(Operator):
462     '''Set a context array value. '''
463     '''Useful for cycling the active mesh edit mode'''
464     bl_idname = "wm.context_cycle_array"
465     bl_label = "Context Array Cycle"
466     bl_options = {'UNDO', 'INTERNAL'}
467
468     data_path = rna_path_prop
469     reverse = rna_reverse_prop
470
471     def execute(self, context):
472         data_path = self.data_path
473         value = context_path_validate(context, data_path)
474         if value is Ellipsis:
475             return {'PASS_THROUGH'}
476
477         def cycle(array):
478             if self.reverse:
479                 array.insert(0, array.pop())
480             else:
481                 array.append(array.pop(0))
482             return array
483
484         exec("context.%s = cycle(context.%s[:])" % (data_path, data_path))
485
486         return operator_path_undo_return(context, data_path)
487
488
489 class WM_MT_context_menu_enum(Menu):
490     bl_label = ""
491     data_path = ""  # BAD DESIGN, set from operator below.
492
493     def draw(self, context):
494         data_path = self.data_path
495         value = context_path_validate(bpy.context, data_path)
496         if value is Ellipsis:
497             return {'PASS_THROUGH'}
498         base_path, prop_string = data_path.rsplit(".", 1)
499         value_base = context_path_validate(context, base_path)
500
501         values = [(i.name, i.identifier) for i in value_base.bl_rna.properties[prop_string].enum_items]
502
503         for name, identifier in values:
504             prop = self.layout.operator("wm.context_set_enum", text=name)
505             prop.data_path = data_path
506             prop.value = identifier
507
508
509 class WM_OT_context_menu_enum(Operator):
510     bl_idname = "wm.context_menu_enum"
511     bl_label = "Context Enum Menu"
512     bl_options = {'UNDO', 'INTERNAL'}
513     data_path = rna_path_prop
514
515     def execute(self, context):
516         data_path = self.data_path
517         WM_MT_context_menu_enum.data_path = data_path
518         bpy.ops.wm.call_menu(name="WM_MT_context_menu_enum")
519         return {'PASS_THROUGH'}
520
521
522 class WM_OT_context_set_id(Operator):
523     '''Toggle a context value'''
524     bl_idname = "wm.context_set_id"
525     bl_label = "Set Library ID"
526     bl_options = {'UNDO', 'INTERNAL'}
527
528     data_path = rna_path_prop
529     value = StringProperty(
530             name="Value",
531             description="Assign value",
532             maxlen=1024,
533             )
534
535     def execute(self, context):
536         value = self.value
537         data_path = self.data_path
538
539         # match the pointer type from the target property to bpy.data.*
540         # so we lookup the correct list.
541         data_path_base, data_path_prop = data_path.rsplit(".", 1)
542         data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
543         data_prop_rna_type = data_prop_rna.fixed_type
544
545         id_iter = None
546
547         for prop in bpy.data.rna_type.properties:
548             if prop.rna_type.identifier == "CollectionProperty":
549                 if prop.fixed_type == data_prop_rna_type:
550                     id_iter = prop.identifier
551                     break
552
553         if id_iter:
554             value_id = getattr(bpy.data, id_iter).get(value)
555             exec("context.%s = value_id" % data_path)
556
557         return operator_path_undo_return(context, data_path)
558
559
560 doc_id = StringProperty(
561         name="Doc ID",
562         maxlen=1024,
563         options={'HIDDEN'},
564         )
565
566 doc_new = StringProperty(
567         name="Edit Description",
568         maxlen=1024,
569         )
570
571 data_path_iter = StringProperty(
572         description="The data path relative to the context, must point to an iterable")
573
574 data_path_item = StringProperty(
575         description="The data path from each iterable to the value (int or float)")
576
577
578 class WM_OT_context_collection_boolean_set(Operator):
579     '''Set boolean values for a collection of items'''
580     bl_idname = "wm.context_collection_boolean_set"
581     bl_label = "Context Collection Boolean Set"
582     bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
583
584     data_path_iter = data_path_iter
585     data_path_item = data_path_item
586
587     type = EnumProperty(
588             name="Type",
589             items=(('TOGGLE', "Toggle", ""),
590                    ('ENABLE', "Enable", ""),
591                    ('DISABLE', "Disable", ""),
592                    ),
593             )
594
595     def execute(self, context):
596         data_path_iter = self.data_path_iter
597         data_path_item = self.data_path_item
598
599         items = list(getattr(context, data_path_iter))
600         items_ok = []
601         is_set = False
602         for item in items:
603             try:
604                 value_orig = eval("item." + data_path_item)
605             except:
606                 continue
607
608             if value_orig == True:
609                 is_set = True
610             elif value_orig == False:
611                 pass
612             else:
613                 self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
614                             (data_path_iter, data_path_item))
615                 return {'CANCELLED'}
616
617             items_ok.append(item)
618
619         # avoid undo push when nothing to do
620         if not items_ok:
621             return {'CANCELLED'}
622
623         if self.type == 'ENABLE':
624             is_set = True
625         elif self.type == 'DISABLE':
626             is_set = False
627         else:
628             is_set = not is_set
629
630         exec_str = "item.%s = %s" % (data_path_item, is_set)
631         for item in items_ok:
632             exec(exec_str)
633
634         return operator_value_undo_return(item)
635
636
637 class WM_OT_context_modal_mouse(Operator):
638     '''Adjust arbitrary values with mouse input'''
639     bl_idname = "wm.context_modal_mouse"
640     bl_label = "Context Modal Mouse"
641     bl_options = {'GRAB_POINTER', 'BLOCKING', 'UNDO', 'INTERNAL'}
642
643     data_path_iter = data_path_iter
644     data_path_item = data_path_item
645
646     input_scale = FloatProperty(
647             description="Scale the mouse movement by this value before applying the delta",
648             default=0.01,
649             )
650     invert = BoolProperty(
651             description="Invert the mouse input",
652             default=False,
653             )
654     initial_x = IntProperty(options={'HIDDEN'})
655
656     def _values_store(self, context):
657         data_path_iter = self.data_path_iter
658         data_path_item = self.data_path_item
659
660         self._values = values = {}
661
662         for item in getattr(context, data_path_iter):
663             try:
664                 value_orig = eval("item." + data_path_item)
665             except:
666                 continue
667
668             # check this can be set, maybe this is library data.
669             try:
670                 exec("item.%s = %s" % (data_path_item, value_orig))
671             except:
672                 continue
673
674             values[item] = value_orig
675
676     def _values_delta(self, delta):
677         delta *= self.input_scale
678         if self.invert:
679             delta = - delta
680
681         data_path_item = self.data_path_item
682         for item, value_orig in self._values.items():
683             if type(value_orig) == int:
684                 exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
685             else:
686                 exec("item.%s = %f" % (data_path_item, value_orig + delta))
687
688     def _values_restore(self):
689         data_path_item = self.data_path_item
690         for item, value_orig in self._values.items():
691             exec("item.%s = %s" % (data_path_item, value_orig))
692
693         self._values.clear()
694
695     def _values_clear(self):
696         self._values.clear()
697
698     def modal(self, context, event):
699         event_type = event.type
700
701         if event_type == 'MOUSEMOVE':
702             delta = event.mouse_x - self.initial_x
703             self._values_delta(delta)
704
705         elif 'LEFTMOUSE' == event_type:
706             item = next(iter(self._values.keys()))
707             self._values_clear()
708             return operator_value_undo_return(item)
709
710         elif event_type in {'RIGHTMOUSE', 'ESC'}:
711             self._values_restore()
712             return {'CANCELLED'}
713
714         return {'RUNNING_MODAL'}
715
716     def invoke(self, context, event):
717         self._values_store(context)
718
719         if not self._values:
720             self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
721                     (self.data_path_iter, self.data_path_item))
722
723             return {'CANCELLED'}
724         else:
725             self.initial_x = event.mouse_x
726
727             context.window_manager.modal_handler_add(self)
728             return {'RUNNING_MODAL'}
729
730
731 class WM_OT_url_open(Operator):
732     "Open a website in the Webbrowser"
733     bl_idname = "wm.url_open"
734     bl_label = ""
735
736     url = StringProperty(
737             name="URL",
738             description="URL to open",
739             )
740
741     def execute(self, context):
742         import webbrowser
743         webbrowser.open(self.url)
744         return {'FINISHED'}
745
746
747 class WM_OT_path_open(Operator):
748     "Open a path in a file browser"
749     bl_idname = "wm.path_open"
750     bl_label = ""
751
752     filepath = StringProperty(
753             subtype='FILE_PATH',
754             )
755
756     def execute(self, context):
757         import sys
758         import os
759         import subprocess
760
761         filepath = bpy.path.abspath(self.filepath)
762         filepath = os.path.normpath(filepath)
763
764         if not os.path.exists(filepath):
765             self.report({'ERROR'}, "File '%s' not found" % filepath)
766             return {'CANCELLED'}
767
768         if sys.platform[:3] == "win":
769             subprocess.Popen(['start', filepath], shell=True)
770         elif sys.platform == 'darwin':
771             subprocess.Popen(['open', filepath])
772         else:
773             try:
774                 subprocess.Popen(['xdg-open', filepath])
775             except OSError:
776                 # xdg-open *should* be supported by recent Gnome, KDE, Xfce
777                 pass
778
779         return {'FINISHED'}
780
781
782 class WM_OT_doc_view(Operator):
783     '''Load online reference docs'''
784     bl_idname = "wm.doc_view"
785     bl_label = "View Documentation"
786
787     doc_id = doc_id
788     if bpy.app.version_cycle == "release":
789         _prefix = ("http://www.blender.org/documentation/blender_python_api_%s%s_release" %
790                    ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char))
791     else:
792         _prefix = ("http://www.blender.org/documentation/blender_python_api_%s" %
793                    "_".join(str(v) for v in bpy.app.version))
794
795     def _nested_class_string(self, class_string):
796         ls = []
797         class_obj = getattr(bpy.types, class_string, None).bl_rna
798         while class_obj:
799             ls.insert(0, class_obj)
800             class_obj = class_obj.nested
801         return '.'.join(class_obj.identifier for class_obj in ls)
802
803     def execute(self, context):
804         id_split = self.doc_id.split('.')
805         if len(id_split) == 1:  # rna, class
806             url = '%s/bpy.types.%s.html' % (self._prefix, id_split[0])
807         elif len(id_split) == 2:  # rna, class.prop
808             class_name, class_prop = id_split
809
810             if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
811                 url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
812                        (self._prefix, class_name, class_name, class_prop))
813             else:
814
815                 # detect if this is a inherited member and use that name instead
816                 rna_parent = getattr(bpy.types, class_name).bl_rna
817                 rna_prop = rna_parent.properties[class_prop]
818                 rna_parent = rna_parent.base
819                 while rna_parent and rna_prop == rna_parent.properties.get(class_prop):
820                     class_name = rna_parent.identifier
821                     rna_parent = rna_parent.base
822
823                 #~ class_name_full = self._nested_class_string(class_name)
824                 url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" %
825                        (self._prefix, class_name, class_name, class_prop))
826
827         else:
828             return {'PASS_THROUGH'}
829
830         import webbrowser
831         webbrowser.open(url)
832
833         return {'FINISHED'}
834
835
836 class WM_OT_doc_edit(Operator):
837     '''Load online reference docs'''
838     bl_idname = "wm.doc_edit"
839     bl_label = "Edit Documentation"
840
841     doc_id = doc_id
842     doc_new = doc_new
843
844     _url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
845
846     def _send_xmlrpc(self, data_dict):
847         print("sending data:", data_dict)
848
849         import xmlrpc.client
850         user = 'blenderuser'
851         pwd = 'blender>user'
852
853         docblog = xmlrpc.client.ServerProxy(self._url)
854         docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
855
856     def execute(self, context):
857
858         doc_id = self.doc_id
859         doc_new = self.doc_new
860
861         class_name, class_prop = doc_id.split('.')
862
863         if not doc_new:
864             self.report({'ERROR'}, "No input given for '%s'" % doc_id)
865             return {'CANCELLED'}
866
867         # check if this is an operator
868         op_name = class_name.upper() + '_OT_' + class_prop
869         op_class = getattr(bpy.types, op_name, None)
870
871         # Upload this to the web server
872         upload = {}
873
874         if op_class:
875             rna = op_class.bl_rna
876             doc_orig = rna.description
877             if doc_orig == doc_new:
878                 return {'RUNNING_MODAL'}
879
880             print("op - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
881             upload["title"] = 'OPERATOR %s:%s' % (doc_id, doc_orig)
882         else:
883             rna = getattr(bpy.types, class_name).bl_rna
884             doc_orig = rna.properties[class_prop].description
885             if doc_orig == doc_new:
886                 return {'RUNNING_MODAL'}
887
888             print("rna - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
889             upload["title"] = 'RNA %s:%s' % (doc_id, doc_orig)
890
891         upload["description"] = doc_new
892
893         self._send_xmlrpc(upload)
894
895         return {'FINISHED'}
896
897     def draw(self, context):
898         layout = self.layout
899         layout.label(text="Descriptor ID: '%s'" % self.doc_id)
900         layout.prop(self, "doc_new", text="")
901
902     def invoke(self, context, event):
903         wm = context.window_manager
904         return wm.invoke_props_dialog(self, width=600)
905
906
907 rna_path = StringProperty(
908         name="Property Edit",
909         description="Property data_path edit",
910         maxlen=1024,
911         options={'HIDDEN'},
912         )
913
914 rna_value = StringProperty(
915         name="Property Value",
916         description="Property value edit",
917         maxlen=1024,
918         )
919
920 rna_property = StringProperty(
921         name="Property Name",
922         description="Property name edit",
923         maxlen=1024,
924         )
925
926 rna_min = FloatProperty(
927         name="Min",
928         default=0.0,
929         precision=3,
930         )
931
932 rna_max = FloatProperty(
933         name="Max",
934         default=1.0,
935         precision=3,
936         )
937
938
939 class WM_OT_properties_edit(Operator):
940     '''Internal use (edit a property data_path)'''
941     bl_idname = "wm.properties_edit"
942     bl_label = "Edit Property"
943     bl_options = {'REGISTER'}  # only because invoke_props_popup requires.
944
945     data_path = rna_path
946     property = rna_property
947     value = rna_value
948     min = rna_min
949     max = rna_max
950     description = StringProperty(
951             name="Tip",
952             )
953
954     def execute(self, context):
955         data_path = self.data_path
956         value = self.value
957         prop = self.property
958
959         prop_old = getattr(self, "_last_prop", [None])[0]
960
961         if prop_old is None:
962             self.report({'ERROR'}, "Direct execution not supported")
963             return {'CANCELLED'}
964
965         try:
966             value_eval = eval(value)
967         except:
968             value_eval = value
969
970         # First remove
971         item = eval("context.%s" % data_path)
972
973         rna_idprop_ui_prop_clear(item, prop_old)
974         exec_str = "del item['%s']" % prop_old
975         # print(exec_str)
976         exec(exec_str)
977
978         # Reassign
979         exec_str = "item['%s'] = %s" % (prop, repr(value_eval))
980         # print(exec_str)
981         exec(exec_str)
982         self._last_prop[:] = [prop]
983
984         prop_type = type(item[prop])
985
986         prop_ui = rna_idprop_ui_prop_get(item, prop)
987
988         if prop_type in {float, int}:
989
990             prop_ui['soft_min'] = prop_ui['min'] = prop_type(self.min)
991             prop_ui['soft_max'] = prop_ui['max'] = prop_type(self.max)
992
993         prop_ui['description'] = self.description
994
995         # otherwise existing buttons which reference freed
996         # memory may crash blender [#26510]
997         # context.area.tag_redraw()
998         for win in context.window_manager.windows:
999             for area in win.screen.areas:
1000                 area.tag_redraw()
1001
1002         return {'FINISHED'}
1003
1004     def invoke(self, context, event):
1005         data_path = self.data_path
1006
1007         if not data_path:
1008             self.report({'ERROR'}, "Data path not set")
1009             return {'CANCELLED'}
1010
1011         self._last_prop = [self.property]
1012
1013         item = eval("context.%s" % data_path)
1014
1015         # setup defaults
1016         prop_ui = rna_idprop_ui_prop_get(item, self.property, False)  # don't create
1017         if prop_ui:
1018             self.min = prop_ui.get("min", -1000000000)
1019             self.max = prop_ui.get("max", 1000000000)
1020             self.description = prop_ui.get("description", "")
1021
1022         wm = context.window_manager
1023         return wm.invoke_props_dialog(self)
1024
1025
1026 class WM_OT_properties_add(Operator):
1027     '''Internal use (edit a property data_path)'''
1028     bl_idname = "wm.properties_add"
1029     bl_label = "Add Property"
1030
1031     data_path = rna_path
1032
1033     def execute(self, context):
1034         data_path = self.data_path
1035         item = eval("context.%s" % data_path)
1036
1037         def unique_name(names):
1038             prop = 'prop'
1039             prop_new = prop
1040             i = 1
1041             while prop_new in names:
1042                 prop_new = prop + str(i)
1043                 i += 1
1044
1045             return prop_new
1046
1047         property = unique_name(item.keys())
1048
1049         item[property] = 1.0
1050         return {'FINISHED'}
1051
1052
1053 class WM_OT_properties_context_change(Operator):
1054     "Change the context tab in a Properties Window"
1055     bl_idname = "wm.properties_context_change"
1056     bl_label = ""
1057
1058     context = StringProperty(
1059             name="Context",
1060             maxlen=32,
1061             )
1062
1063     def execute(self, context):
1064         context.space_data.context = self.context
1065         return {'FINISHED'}
1066
1067
1068 class WM_OT_properties_remove(Operator):
1069     '''Internal use (edit a property data_path)'''
1070     bl_idname = "wm.properties_remove"
1071     bl_label = "Remove Property"
1072
1073     data_path = rna_path
1074     property = rna_property
1075
1076     def execute(self, context):
1077         data_path = self.data_path
1078         item = eval("context.%s" % data_path)
1079         del item[self.property]
1080         return {'FINISHED'}
1081
1082
1083 class WM_OT_keyconfig_activate(Operator):
1084     bl_idname = "wm.keyconfig_activate"
1085     bl_label = "Activate Keyconfig"
1086
1087     filepath = StringProperty(
1088             subtype='FILE_PATH',
1089             )
1090
1091     def execute(self, context):
1092         bpy.utils.keyconfig_set(self.filepath)
1093         return {'FINISHED'}
1094
1095
1096 class WM_OT_appconfig_default(Operator):
1097     bl_idname = "wm.appconfig_default"
1098     bl_label = "Default Application Configuration"
1099
1100     def execute(self, context):
1101         import os
1102
1103         context.window_manager.keyconfigs.active = context.window_manager.keyconfigs.default
1104
1105         filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py")
1106
1107         if os.path.exists(filepath):
1108             bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
1109
1110         return {'FINISHED'}
1111
1112
1113 class WM_OT_appconfig_activate(Operator):
1114     bl_idname = "wm.appconfig_activate"
1115     bl_label = "Activate Application Configuration"
1116
1117     filepath = StringProperty(
1118             subtype='FILE_PATH',
1119             )
1120
1121     def execute(self, context):
1122         import os
1123         bpy.utils.keyconfig_set(self.filepath)
1124
1125         filepath = self.filepath.replace("keyconfig", "interaction")
1126
1127         if os.path.exists(filepath):
1128             bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
1129
1130         return {'FINISHED'}
1131
1132
1133 class WM_OT_sysinfo(Operator):
1134     '''Generate System Info'''
1135     bl_idname = "wm.sysinfo"
1136     bl_label = "System Info"
1137
1138     def execute(self, context):
1139         import sys_info
1140         sys_info.write_sysinfo(self)
1141         return {'FINISHED'}
1142
1143
1144 class WM_OT_copy_prev_settings(Operator):
1145     '''Copy settings from previous version'''
1146     bl_idname = "wm.copy_prev_settings"
1147     bl_label = "Copy Previous Settings"
1148
1149     def execute(self, context):
1150         import os
1151         import shutil
1152         ver = bpy.app.version
1153         ver_old = ((ver[0] * 100) + ver[1]) - 1
1154         path_src = bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100)
1155         path_dst = bpy.utils.resource_path('USER')
1156
1157         if os.path.isdir(path_dst):
1158             self.report({'ERROR'}, "Target path %r exists" % path_dst)
1159         elif not os.path.isdir(path_src):
1160             self.report({'ERROR'}, "Source path %r exists" % path_src)
1161         else:
1162             shutil.copytree(path_src, path_dst, symlinks=True)
1163
1164             # in 2.57 and earlier windows installers, system scripts were copied
1165             # into the configuration directory, don't want to copy those
1166             system_script = os.path.join(path_dst, 'scripts/modules/bpy_types.py')
1167             if os.path.isfile(system_script):
1168                 shutil.rmtree(os.path.join(path_dst, 'scripts'))
1169                 shutil.rmtree(os.path.join(path_dst, 'plugins'))
1170
1171             # don't loose users work if they open the splash later.
1172             if bpy.data.is_saved is bpy.data.is_dirty is False:
1173                 bpy.ops.wm.read_homefile()
1174             else:
1175                 self.report({'INFO'}, "Reload Start-Up file to restore settings")
1176             return {'FINISHED'}
1177
1178         return {'CANCELLED'}
1179
1180 class WM_OT_blenderplayer_start(bpy.types.Operator):
1181     '''Launch the Blenderplayer with the current blendfile'''
1182     bl_idname = "wm.blenderplayer_start"
1183     bl_label = "Start"
1184     
1185     blender_bin_path = bpy.app.binary_path
1186     blender_bin_dir = os.path.dirname(blender_bin_path)
1187     ext = os.path.splitext(blender_bin_path)[-1]
1188     player_path = os.path.join(blender_bin_dir, 'blenderplayer' + ext)
1189     
1190     def execute(self, context):
1191         import sys
1192
1193         if sys.platform == 'darwin':
1194             self.player_path = os.path.join(self.blender_bin_dir, '../../../blenderplayer.app/Contents/MacOS/blenderplayer')
1195         
1196         filepath = bpy.app.tempdir + "game.blend"
1197         bpy.ops.wm.save_as_mainfile(filepath=filepath, check_existing=False, copy=True)
1198         subprocess.call([self.player_path, filepath])
1199         return {'FINISHED'}
1200
1201 class WM_OT_keyconfig_test(Operator):
1202     "Test keyconfig for conflicts"
1203     bl_idname = "wm.keyconfig_test"
1204     bl_label = "Test Key Configuration for Conflicts"
1205
1206     def execute(self, context):
1207         from bpy_extras import keyconfig_utils
1208
1209         wm = context.window_manager
1210         kc = wm.keyconfigs.default
1211
1212         if keyconfig_utils.keyconfig_test(kc):
1213             print("CONFLICT")
1214
1215         return {'FINISHED'}
1216
1217
1218 class WM_OT_keyconfig_import(Operator):
1219     "Import key configuration from a python script"
1220     bl_idname = "wm.keyconfig_import"
1221     bl_label = "Import Key Configuration..."
1222
1223     filepath = StringProperty(
1224             subtype='FILE_PATH',
1225             default="keymap.py",
1226             )
1227     filter_folder = BoolProperty(
1228             name="Filter folders",
1229             default=True,
1230             options={'HIDDEN'},
1231             )
1232     filter_text = BoolProperty(
1233             name="Filter text",
1234             default=True,
1235             options={'HIDDEN'},
1236             )
1237     filter_python = BoolProperty(
1238             name="Filter python",
1239             default=True,
1240             options={'HIDDEN'},
1241             )
1242     keep_original = BoolProperty(
1243             name="Keep original",
1244             description="Keep original file after copying to configuration folder",
1245             default=True,
1246             )
1247
1248     def execute(self, context):
1249         import os
1250         from os.path import basename
1251         import shutil
1252
1253         if not self.filepath:
1254             self.report({'ERROR'}, "Filepath not set")
1255             return {'CANCELLED'}
1256
1257         config_name = basename(self.filepath)
1258
1259         path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True)
1260         path = os.path.join(path, config_name)
1261
1262         try:
1263             if self.keep_original:
1264                 shutil.copy(self.filepath, path)
1265             else:
1266                 shutil.move(self.filepath, path)
1267         except Exception as e:
1268             self.report({'ERROR'}, "Installing keymap failed: %s" % e)
1269             return {'CANCELLED'}
1270
1271         # sneaky way to check we're actually running the code.
1272         bpy.utils.keyconfig_set(path)
1273
1274         return {'FINISHED'}
1275
1276     def invoke(self, context, event):
1277         wm = context.window_manager
1278         wm.fileselect_add(self)
1279         return {'RUNNING_MODAL'}
1280
1281 # This operator is also used by interaction presets saving - AddPresetBase
1282
1283
1284 class WM_OT_keyconfig_export(Operator):
1285     "Export key configuration to a python script"
1286     bl_idname = "wm.keyconfig_export"
1287     bl_label = "Export Key Configuration..."
1288
1289     filepath = StringProperty(
1290             subtype='FILE_PATH',
1291             default="keymap.py",
1292             )
1293     filter_folder = BoolProperty(
1294             name="Filter folders",
1295             default=True,
1296             options={'HIDDEN'},
1297             )
1298     filter_text = BoolProperty(
1299             name="Filter text",
1300             default=True,
1301             options={'HIDDEN'},
1302             )
1303     filter_python = BoolProperty(
1304             name="Filter python",
1305             default=True,
1306             options={'HIDDEN'},
1307             )
1308
1309     def execute(self, context):
1310         from bpy_extras import keyconfig_utils
1311
1312         if not self.filepath:
1313             raise Exception("Filepath not set")
1314
1315         if not self.filepath.endswith('.py'):
1316             self.filepath += '.py'
1317
1318         wm = context.window_manager
1319
1320         keyconfig_utils.keyconfig_export(wm,
1321                                          wm.keyconfigs.active,
1322                                          self.filepath,
1323                                          )
1324
1325         return {'FINISHED'}
1326
1327     def invoke(self, context, event):
1328         wm = context.window_manager
1329         wm.fileselect_add(self)
1330         return {'RUNNING_MODAL'}
1331
1332
1333 class WM_OT_keymap_restore(Operator):
1334     "Restore key map(s)"
1335     bl_idname = "wm.keymap_restore"
1336     bl_label = "Restore Key Map(s)"
1337
1338     all = BoolProperty(
1339             name="All Keymaps",
1340             description="Restore all keymaps to default",
1341             )
1342
1343     def execute(self, context):
1344         wm = context.window_manager
1345
1346         if self.all:
1347             for km in wm.keyconfigs.user.keymaps:
1348                 km.restore_to_default()
1349         else:
1350             km = context.keymap
1351             km.restore_to_default()
1352
1353         return {'FINISHED'}
1354
1355
1356 class WM_OT_keyitem_restore(Operator):
1357     "Restore key map item"
1358     bl_idname = "wm.keyitem_restore"
1359     bl_label = "Restore Key Map Item"
1360
1361     item_id = IntProperty(
1362             name="Item Identifier",
1363             description="Identifier of the item to remove",
1364             )
1365
1366     @classmethod
1367     def poll(cls, context):
1368         keymap = getattr(context, "keymap", None)
1369         return keymap
1370
1371     def execute(self, context):
1372         km = context.keymap
1373         kmi = km.keymap_items.from_id(self.item_id)
1374
1375         if (not kmi.is_user_defined) and kmi.is_user_modified:
1376             km.restore_item_to_default(kmi)
1377
1378         return {'FINISHED'}
1379
1380
1381 class WM_OT_keyitem_add(Operator):
1382     "Add key map item"
1383     bl_idname = "wm.keyitem_add"
1384     bl_label = "Add Key Map Item"
1385
1386     def execute(self, context):
1387         km = context.keymap
1388
1389         if km.is_modal:
1390             km.keymap_items.new_modal("", 'A', 'PRESS')
1391         else:
1392             km.keymap_items.new("none", 'A', 'PRESS')
1393
1394         # clear filter and expand keymap so we can see the newly added item
1395         if context.space_data.filter_text != "":
1396             context.space_data.filter_text = ""
1397             km.show_expanded_items = True
1398             km.show_expanded_children = True
1399
1400         return {'FINISHED'}
1401
1402
1403 class WM_OT_keyitem_remove(Operator):
1404     "Remove key map item"
1405     bl_idname = "wm.keyitem_remove"
1406     bl_label = "Remove Key Map Item"
1407
1408     item_id = IntProperty(
1409             name="Item Identifier",
1410             description="Identifier of the item to remove",
1411             )
1412
1413     @classmethod
1414     def poll(cls, context):
1415         return hasattr(context, "keymap")
1416
1417     def execute(self, context):
1418         km = context.keymap
1419         kmi = km.keymap_items.from_id(self.item_id)
1420         km.keymap_items.remove(kmi)
1421         return {'FINISHED'}
1422
1423
1424 class WM_OT_keyconfig_remove(Operator):
1425     "Remove key config"
1426     bl_idname = "wm.keyconfig_remove"
1427     bl_label = "Remove Key Config"
1428
1429     @classmethod
1430     def poll(cls, context):
1431         wm = context.window_manager
1432         keyconf = wm.keyconfigs.active
1433         return keyconf and keyconf.is_user_defined
1434
1435     def execute(self, context):
1436         wm = context.window_manager
1437         keyconfig = wm.keyconfigs.active
1438         wm.keyconfigs.remove(keyconfig)
1439         return {'FINISHED'}
1440
1441
1442 class WM_OT_operator_cheat_sheet(Operator):
1443     bl_idname = "wm.operator_cheat_sheet"
1444     bl_label = "Operator Cheat Sheet"
1445
1446     def execute(self, context):
1447         op_strings = []
1448         tot = 0
1449         for op_module_name in dir(bpy.ops):
1450             op_module = getattr(bpy.ops, op_module_name)
1451             for op_submodule_name in dir(op_module):
1452                 op = getattr(op_module, op_submodule_name)
1453                 text = repr(op)
1454                 if text.split("\n")[-1].startswith('bpy.ops.'):
1455                     op_strings.append(text)
1456                     tot += 1
1457
1458             op_strings.append('')
1459
1460         textblock = bpy.data.texts.new("OperatorList.txt")
1461         textblock.write('# %d Operators\n\n' % tot)
1462         textblock.write('\n'.join(op_strings))
1463         self.report({'INFO'}, "See OperatorList.txt textblock")
1464         return {'FINISHED'}
1465
1466
1467 class WM_OT_addon_enable(Operator):
1468     "Enable an addon"
1469     bl_idname = "wm.addon_enable"
1470     bl_label = "Enable Addon"
1471
1472     module = StringProperty(
1473             name="Module",
1474             description="Module name of the addon to enable",
1475             )
1476
1477     def execute(self, context):
1478         import addon_utils
1479
1480         mod = addon_utils.enable(self.module)
1481
1482         if mod:
1483             info = addon_utils.module_bl_info(mod)
1484
1485             info_ver = info.get("blender", (0, 0, 0))
1486
1487             if info_ver > bpy.app.version:
1488                 self.report({'WARNING'}, ("This script was written Blender "
1489                                           "version %d.%d.%d and might not "
1490                                           "function (correctly), "
1491                                           "though it is enabled") %
1492                                          info_ver)
1493             return {'FINISHED'}
1494         else:
1495             return {'CANCELLED'}
1496
1497
1498 class WM_OT_addon_disable(Operator):
1499     "Disable an addon"
1500     bl_idname = "wm.addon_disable"
1501     bl_label = "Disable Addon"
1502
1503     module = StringProperty(
1504             name="Module",
1505             description="Module name of the addon to disable",
1506             )
1507
1508     def execute(self, context):
1509         import addon_utils
1510
1511         addon_utils.disable(self.module)
1512         return {'FINISHED'}
1513
1514
1515 class WM_OT_addon_install(Operator):
1516     "Install an addon"
1517     bl_idname = "wm.addon_install"
1518     bl_label = "Install Addon..."
1519
1520     overwrite = BoolProperty(
1521             name="Overwrite",
1522             description="Remove existing addons with the same ID",
1523             default=True,
1524             )
1525     target = EnumProperty(
1526             name="Target Path",
1527             items=(('DEFAULT', "Default", ""),
1528                    ('PREFS', "User Prefs", "")),
1529             )
1530
1531     filepath = StringProperty(
1532             subtype='FILE_PATH',
1533             )
1534     filter_folder = BoolProperty(
1535             name="Filter folders",
1536             default=True,
1537             options={'HIDDEN'},
1538             )
1539     filter_python = BoolProperty(
1540             name="Filter python",
1541             default=True,
1542             options={'HIDDEN'},
1543             )
1544     filter_glob = StringProperty(
1545             default="*.py;*.zip",
1546             options={'HIDDEN'},
1547             )
1548
1549     @staticmethod
1550     def _module_remove(path_addons, module):
1551         import os
1552         module = os.path.splitext(module)[0]
1553         for f in os.listdir(path_addons):
1554             f_base = os.path.splitext(f)[0]
1555             if f_base == module:
1556                 f_full = os.path.join(path_addons, f)
1557
1558                 if os.path.isdir(f_full):
1559                     os.rmdir(f_full)
1560                 else:
1561                     os.remove(f_full)
1562
1563     def execute(self, context):
1564         import addon_utils
1565         import traceback
1566         import zipfile
1567         import shutil
1568         import os
1569
1570         pyfile = self.filepath
1571
1572         if self.target == 'DEFAULT':
1573             # don't use bpy.utils.script_paths("addons") because we may not be able to write to it.
1574             path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True)
1575         else:
1576             path_addons = bpy.context.user_preferences.filepaths.script_directory
1577             if path_addons:
1578                 path_addons = os.path.join(path_addons, "addons")
1579
1580         if not path_addons:
1581             self.report({'ERROR'}, "Failed to get addons path")
1582             return {'CANCELLED'}
1583
1584         # create dir is if missing.
1585         if not os.path.exists(path_addons):
1586             os.makedirs(path_addons)
1587
1588         # Check if we are installing from a target path,
1589         # doing so causes 2+ addons of same name or when the same from/to
1590         # location is used, removal of the file!
1591         addon_path = ""
1592         pyfile_dir = os.path.dirname(pyfile)
1593         for addon_path in addon_utils.paths():
1594             if os.path.samefile(pyfile_dir, addon_path):
1595                 self.report({'ERROR'}, "Source file is in the addon search path: %r" % addon_path)
1596                 return {'CANCELLED'}
1597         del addon_path
1598         del pyfile_dir
1599         # done checking for exceptional case
1600
1601         addons_old = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)}
1602
1603         #check to see if the file is in compressed format (.zip)
1604         if zipfile.is_zipfile(pyfile):
1605             try:
1606                 file_to_extract = zipfile.ZipFile(pyfile, 'r')
1607             except:
1608                 traceback.print_exc()
1609                 return {'CANCELLED'}
1610
1611             if self.overwrite:
1612                 for f in file_to_extract.namelist():
1613                     WM_OT_addon_install._module_remove(path_addons, f)
1614             else:
1615                 for f in file_to_extract.namelist():
1616                     path_dest = os.path.join(path_addons, os.path.basename(f))
1617                     if os.path.exists(path_dest):
1618                         self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1619                         return {'CANCELLED'}
1620
1621             try:  # extract the file to "addons"
1622                 file_to_extract.extractall(path_addons)
1623
1624                 # zip files can create this dir with metadata, don't need it
1625                 macosx_dir = os.path.join(path_addons, '__MACOSX')
1626                 if os.path.isdir(macosx_dir):
1627                     shutil.rmtree(macosx_dir)
1628
1629             except:
1630                 traceback.print_exc()
1631                 return {'CANCELLED'}
1632
1633         else:
1634             path_dest = os.path.join(path_addons, os.path.basename(pyfile))
1635
1636             if self.overwrite:
1637                 WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile))
1638             elif os.path.exists(path_dest):
1639                 self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
1640                 return {'CANCELLED'}
1641
1642             #if not compressed file just copy into the addon path
1643             try:
1644                 shutil.copyfile(pyfile, path_dest)
1645
1646             except:
1647                 traceback.print_exc()
1648                 return {'CANCELLED'}
1649
1650         addons_new = {mod.__name__ for mod in addon_utils.modules(addon_utils.addons_fake_modules)} - addons_old
1651         addons_new.discard("modules")
1652
1653         # disable any addons we may have enabled previously and removed.
1654         # this is unlikely but do just in case. bug [#23978]
1655         for new_addon in addons_new:
1656             addon_utils.disable(new_addon)
1657
1658         # possible the zip contains multiple addons, we could disallow this
1659         # but for now just use the first
1660         for mod in addon_utils.modules(addon_utils.addons_fake_modules):
1661             if mod.__name__ in addons_new:
1662                 info = addon_utils.module_bl_info(mod)
1663
1664                 # show the newly installed addon.
1665                 context.window_manager.addon_filter = 'All'
1666                 context.window_manager.addon_search = info["name"]
1667                 break
1668
1669         # in case a new module path was created to install this addon.
1670         bpy.utils.refresh_script_paths()
1671
1672         # TODO, should not be a warning.
1673         #~ self.report({'WARNING'}, "File installed to '%s'\n" % path_dest)
1674         return {'FINISHED'}
1675
1676     def invoke(self, context, event):
1677         wm = context.window_manager
1678         wm.fileselect_add(self)
1679         return {'RUNNING_MODAL'}
1680
1681
1682 class WM_OT_addon_remove(Operator):
1683     "Disable an addon"
1684     bl_idname = "wm.addon_remove"
1685     bl_label = "Remove Addon"
1686
1687     module = StringProperty(
1688             name="Module",
1689             description="Module name of the addon to remove",
1690             )
1691
1692     @staticmethod
1693     def path_from_addon(module):
1694         import os
1695         import addon_utils
1696
1697         for mod in addon_utils.modules(addon_utils.addons_fake_modules):
1698             if mod.__name__ == module:
1699                 filepath = mod.__file__
1700                 if os.path.exists(filepath):
1701                     if os.path.splitext(os.path.basename(filepath))[0] == "__init__":
1702                         return os.path.dirname(filepath), True
1703                     else:
1704                         return filepath, False
1705         return None, False
1706
1707     def execute(self, context):
1708         import addon_utils
1709         import os
1710
1711         path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
1712         if path is None:
1713             self.report('WARNING', "Addon path %r could not be found" % path)
1714             return {'CANCELLED'}
1715
1716         # in case its enabled
1717         addon_utils.disable(self.module)
1718
1719         import shutil
1720         if isdir:
1721             shutil.rmtree(path)
1722         else:
1723             os.remove(path)
1724
1725         context.area.tag_redraw()
1726         return {'FINISHED'}
1727
1728     # lame confirmation check
1729     def draw(self, context):
1730         self.layout.label(text="Remove Addon: %r?" % self.module)
1731         path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
1732         self.layout.label(text="Path: %r" % path)
1733
1734     def invoke(self, context, event):
1735         wm = context.window_manager
1736         return wm.invoke_props_dialog(self, width=600)
1737
1738
1739 class WM_OT_addon_expand(Operator):
1740     "Display more information on this addon"
1741     bl_idname = "wm.addon_expand"
1742     bl_label = ""
1743
1744     module = StringProperty(
1745             name="Module",
1746             description="Module name of the addon to expand",
1747             )
1748
1749     def execute(self, context):
1750         import addon_utils
1751
1752         module_name = self.module
1753
1754         # unlikely to fail, module should have already been imported
1755         try:
1756             # mod = __import__(module_name)
1757             mod = addon_utils.addons_fake_modules.get(module_name)
1758         except:
1759             import traceback
1760             traceback.print_exc()
1761             return {'CANCELLED'}
1762
1763         info = addon_utils.module_bl_info(mod)
1764         info["show_expanded"] = not info["show_expanded"]
1765         return {'FINISHED'}