[Edit Linked Library] Fixes based on code review from Campbell
[blender-addons-contrib.git] / object_edit_linked.py
1 # ***** BEGIN GPL LICENSE BLOCK *****
2 #
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 #
18 # ***** END GPL LICENCE BLOCK *****
19
20 bl_info = {
21     "name": "Edit Linked Library",
22     "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez",
23     "version": (0, 7, 4),
24     "blender": (2, 65, 0),
25     "location": "View3D > Toolshelf > Edit Linked Library",
26     "description": "Allows editing of objects linked from a .blend library.",
27     "wiki_url": "http://wiki.blender.org/index.php?title=Extensions:2.6/Py/Scripts/Object/Edit_Linked_Library",
28     "tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=29630",
29     "category": "Object"}
30
31
32 import bpy
33 from bpy.app.handlers import persistent
34 import os, subprocess
35
36 settings = {
37     "original_file": "",
38     "linked_file": "",
39     "linked_objects": [],
40     }
41
42
43 @persistent
44 def linked_file_check(context):
45     if settings["linked_file"] != "":
46         if os.path.samefile(settings["linked_file"], bpy.data.filepath):
47             print("Editing a linked library.")
48             bpy.ops.object.select_all(action='DESELECT')
49             for ob_name in settings["linked_objects"]:
50                 bpy.data.objects[ob_name].select = True #XXX Assumes selected object is in the active scene
51             if len(settings["linked_objects"]) == 1:
52                 bpy.context.scene.objects.active = bpy.data.objects[settings["linked_objects"][0]]
53         else:
54             # For some reason, the linked editing session ended
55             # (failed to find a file or opened a different file
56             # before returning to the originating .blend)
57             settings["original_file"] = ""
58             settings["linked_file"] = ""
59
60
61 class EditLinked(bpy.types.Operator):
62     """Edit Linked Library"""
63     bl_idname = "object.edit_linked"
64     bl_label = "Edit Linked Library"
65
66     use_autosave = bpy.props.BoolProperty(
67             name="Autosave",
68             description="Save the current file before opening the linked library",
69             default=True)
70     use_instance = bpy.props.BoolProperty(
71             name="New Blender Instance",
72             description="Open in a new Blender instance",
73             default=False)
74
75     @classmethod
76     def poll(cls, context):
77         return settings["original_file"] == "" and (
78                 (context.active_object.dupli_group and
79                  context.active_object.dupli_group.library is not None) or
80                  context.active_object.library is not None)
81         #return context.active_object is not None
82
83     def execute(self, context):
84         #print(bpy.context.active_object.library)
85         target = context.active_object
86
87         if target.dupli_group and target.dupli_group.library:
88             targetpath = target.dupli_group.library.filepath
89             settings["linked_objects"].extend({ob.name for ob in target.dupli_group.objects})
90         elif target.library:
91             targetpath = target.library.filepath
92             settings["linked_objects"].append(target.name)
93
94         if targetpath:
95             print(target.name + " is linked to " + targetpath)
96
97             if self.use_autosave:
98                 bpy.ops.wm.save_mainfile()
99
100             settings["original_file"] = bpy.data.filepath
101
102             # XXX: need to test for proxied rigs
103             settings["linked_file"] = bpy.path.abspath(targetpath)
104
105             if self.use_instance:
106                 try:
107                     subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
108                 except:
109                     print("Error on the new Blender instance")
110                     import traceback
111                     traceback.print_exc()
112             else:
113                 bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
114
115             print("Opened linked file!")
116         else:
117             self.report({'WARNING'}, target.name + " is not linked")
118             print(target.name + " is not linked")
119
120         return {'FINISHED'}
121
122
123 class ReturnToOriginal(bpy.types.Operator):
124     """Load the original file"""
125     bl_idname = "wm.return_to_original"
126     bl_label = "Return to Original File"
127
128     use_autosave = bpy.props.BoolProperty(
129             name="Autosave",
130             description="Save the current file before opening original file",
131             default=True)
132
133     @classmethod
134     def poll(cls, context):
135         # Probably the wrong context to check for here...
136         return (settings["original_file"] != "")
137
138     def execute(self, context):
139         if self.use_autosave:
140             bpy.ops.wm.save_mainfile()
141
142         bpy.ops.wm.open_mainfile(filepath=settings["original_file"])
143
144         settings["original_file"] = ""
145         settings["linked_objects"] = []
146         print("Back to the original!")
147         return {'FINISHED'}
148
149
150 # UI
151 # TODO:Add operators to the File menu?
152 #      Hide the entire panel for non-linked objects?
153 class PanelLinkedEdit(bpy.types.Panel):
154     bl_label = "Edit Linked Library"
155     bl_space_type = "VIEW_3D"
156     bl_region_type = "TOOLS"
157
158     def draw(self, context):
159         layout = self.layout
160         scene = context.scene
161         icon = "OUTLINER_DATA_" + context.active_object.type
162
163         if settings["original_file"] == "" and (
164                 (context.active_object.dupli_group and
165                  context.active_object.dupli_group.library is not None) or
166                 context.active_object.library is not None):
167
168             if (context.active_object.dupli_group is not None):
169                 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
170                                         text="Edit Library: %s" % context.active_object.dupli_group.name)
171             else:
172                 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
173                                         text="Edit Library: %s" % context.active_object.name)
174             props.use_autosave = scene.use_autosave
175             props.use_instance = scene.use_instance
176
177             layout.prop(scene, "use_autosave")
178             layout.prop(scene, "use_instance")
179
180             if (context.active_object.dupli_group is not None):
181                 layout.label(text="Path: %s" %
182                              context.active_object.dupli_group.library.filepath)
183             else:
184                 layout.label(text="Path: %s" %
185                              context.active_object.library.filepath)
186
187         elif settings["original_file"] != "":
188
189             if scene.use_instance:
190                 layout.operator("wm.return_to_original",
191                                 text="Reload Current File",
192                                 icon="FILE_REFRESH").use_autosave = False
193
194                 layout.separator()
195
196                 #XXX - This is for nested linked assets... but it only works
197                 #  when launching a new Blender instance. Nested links don't
198                 #  currently work when using a single instance of Blender.
199                 props = layout.operator("object.edit_linked",
200                                         text="Edit Library: %s" % context.active_object.dupli_group.name,
201                                         icon="LINK_BLEND")
202                 props.use_autosave = scene.use_autosave
203                 props.use_instance = scene.use_instance
204                 layout.prop(scene, "use_autosave")
205                 layout.prop(scene, "use_instance")
206
207                 layout.label(text="Path: %s" %
208                              context.active_object.dupli_group.library.filepath)
209
210             else:
211                 props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
212                 props.use_autosave = scene.use_autosave
213
214                 layout.prop(scene, "use_autosave")
215
216         else:
217             layout.label(text="%s is not linked" % context.active_object.name,
218                          icon=icon)
219
220
221 addon_keymaps = []
222
223
224 def register():
225     bpy.app.handlers.load_post.append(linked_file_check)
226     bpy.utils.register_class(EditLinked)
227     bpy.utils.register_class(ReturnToOriginal)
228     bpy.utils.register_class(PanelLinkedEdit)
229
230     # Is there a better place to store this properties?
231     bpy.types.Scene.use_autosave = bpy.props.BoolProperty(
232             name="Autosave",
233             description="Save the current file before opening a linked file",
234             default=True)
235     bpy.types.Scene.use_instance = bpy.props.BoolProperty(
236             name="New Blender Instance",
237             description="Open in a new Blender instance",
238             default=False)
239
240     # Keymapping (deactivated by default; activated when a library object is selected)
241     kc = bpy.context.window_manager.keyconfigs.addon
242     km = kc.keymaps.new(name="3D View", space_type='VIEW_3D')
243     kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True)
244     kmi.active = True
245     addon_keymaps.append((km, kmi))
246     kmi = km.keymap_items.new("wm.return_to_original", 'NUMPAD_SLASH', 'PRESS', shift=True)
247     kmi.active = True
248     addon_keymaps.append((km, kmi))
249
250
251 def unregister():
252     bpy.utils.unregister_class(EditLinked)
253     bpy.utils.unregister_class(ReturnToOriginal)
254     bpy.utils.unregister_class(PanelLinkedEdit)
255     bpy.app.handlers.load_post.remove(linked_file_check)
256
257     del bpy.types.Scene.use_autosave
258     del bpy.types.Scene.use_instance
259
260     # handle the keymap
261     for km, kmi in addon_keymaps:
262         km.keymap_items.remove(kmi)
263     addon_keymaps.clear()
264
265
266 if __name__ == "__main__":
267     register()