Update addons for changes to proportional edit mode
[blender-addons.git] / object_edit_linked.py
blob269967bcf4f98cc3cc90d54272036725e377a7a6
1 # ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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.
18 # ***** END GPL LICENCE BLOCK *****
20 bl_info = {
21 "name": "Edit Linked Library",
22 "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez, Rainer Trummer",
23 "version": (0, 9, 1),
24 "blender": (2, 80, 0),
25 "location": "File > External Data > Edit Linked Library",
26 "description": "Allows editing of objects linked from a .blend library.",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Object/Edit_Linked_Library",
29 "category": "Object",
32 import bpy
33 import logging
34 import os
36 from bpy.app.handlers import persistent
38 logger = logging.getLogger('object_edit_linked')
40 settings = {
41 "original_file": "",
42 "linked_file": "",
43 "linked_objects": [],
47 @persistent
48 def linked_file_check(context: bpy.context):
49 if settings["linked_file"] != "":
50 if os.path.samefile(settings["linked_file"], bpy.data.filepath):
51 logger.info("Editing a linked library.")
52 bpy.ops.object.select_all(action='DESELECT')
53 for ob_name in settings["linked_objects"]:
54 bpy.data.objects[ob_name].select_set(True) # XXX Assumes selected object is in the active scene
55 if len(settings["linked_objects"]) == 1:
56 context.view_layer.objects.active = bpy.data.objects[settings["linked_objects"][0]]
57 else:
58 # For some reason, the linked editing session ended
59 # (failed to find a file or opened a different file
60 # before returning to the originating .blend)
61 settings["original_file"] = ""
62 settings["linked_file"] = ""
65 class OBJECT_OT_EditLinked(bpy.types.Operator):
66 """Edit Linked Library"""
67 bl_idname = "object.edit_linked"
68 bl_label = "Edit Linked Library"
70 use_autosave: bpy.props.BoolProperty(
71 name="Autosave",
72 description="Save the current file before opening the linked library",
73 default=True)
74 use_instance: bpy.props.BoolProperty(
75 name="New Blender Instance",
76 description="Open in a new Blender instance",
77 default=False)
79 @classmethod
80 def poll(cls, context: bpy.context):
81 return settings["original_file"] == "" and context.active_object is not None and (
82 (context.active_object.instance_collection and
83 context.active_object.instance_collection.library is not None) or
84 (context.active_object.proxy and
85 context.active_object.proxy.library is not None) or
86 context.active_object.library is not None)
88 def execute(self, context: bpy.context):
89 target = context.active_object
91 if target.instance_collection and target.instance_collection.library:
92 targetpath = target.instance_collection.library.filepath
93 settings["linked_objects"].extend({ob.name for ob in target.instance_collection.objects})
94 elif target.library:
95 targetpath = target.library.filepath
96 settings["linked_objects"].append(target.name)
97 elif target.proxy:
98 target = target.proxy
99 targetpath = target.library.filepath
100 settings["linked_objects"].append(target.name)
102 if targetpath:
103 logger.debug(target.name + " is linked to " + targetpath)
105 if self.use_autosave:
106 if not bpy.data.filepath:
107 # File is not saved on disk, better to abort!
108 self.report({'ERROR'}, "Current file does not exist on disk, we cannot autosave it, aborting")
109 return {'CANCELLED'}
110 bpy.ops.wm.save_mainfile()
112 settings["original_file"] = bpy.data.filepath
113 settings["linked_file"] = bpy.path.abspath(targetpath)
115 if self.use_instance:
116 import subprocess
117 try:
118 subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
119 except:
120 logger.error("Error on the new Blender instance")
121 import traceback
122 logger.error(traceback.print_exc())
123 else:
124 bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
126 logger.info("Opened linked file!")
127 else:
128 self.report({'WARNING'}, target.name + " is not linked")
129 logger.warning(target.name + " is not linked")
131 return {'FINISHED'}
134 class WM_OT_ReturnToOriginal(bpy.types.Operator):
135 """Load the original file"""
136 bl_idname = "wm.return_to_original"
137 bl_label = "Return to Original File"
139 use_autosave: bpy.props.BoolProperty(
140 name="Autosave",
141 description="Save the current file before opening original file",
142 default=True)
144 @classmethod
145 def poll(cls, context: bpy.context):
146 return (settings["original_file"] != "")
148 def execute(self, context: bpy.context):
149 if self.use_autosave:
150 bpy.ops.wm.save_mainfile()
152 bpy.ops.wm.open_mainfile(filepath=settings["original_file"])
154 settings["original_file"] = ""
155 settings["linked_objects"] = []
156 logger.info("Back to the original!")
157 return {'FINISHED'}
160 class VIEW3D_PT_PanelLinkedEdit(bpy.types.Panel):
161 bl_label = "Edit Linked Library"
162 bl_space_type = "VIEW_3D"
163 bl_region_type = 'UI'
164 bl_category = "Item"
165 bl_context = 'objectmode'
167 @classmethod
168 def poll(cls, context: bpy.context):
169 return (context.active_object is not None) or (settings["original_file"] != "")
171 def draw_common(self, scene, layout, props):
172 props.use_autosave = scene.use_autosave
173 props.use_instance = scene.use_instance
175 layout.prop(scene, "use_autosave")
176 layout.prop(scene, "use_instance")
178 def draw(self, context: bpy.context):
179 scene = context.scene
180 layout = self.layout
181 layout.use_property_split = True
182 layout.use_property_decorate = False
183 icon = "OUTLINER_DATA_" + context.active_object.type
185 target = None
187 if context.active_object.proxy:
188 target = context.active_object.proxy
189 else:
190 target = context.active_object.instance_collection
192 if settings["original_file"] == "" and (
193 (target and
194 target.library is not None) or
195 context.active_object.library is not None):
197 if (target is not None):
198 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
199 text="Edit Library: %s" % target.name)
200 else:
201 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
202 text="Edit Library: %s" % context.active_object.name)
204 self.draw_common(scene, layout, props)
206 if (target is not None):
207 layout.label(text="Path: %s" %
208 target.library.filepath)
209 else:
210 layout.label(text="Path: %s" %
211 context.active_object.library.filepath)
213 elif settings["original_file"] != "":
215 if scene.use_instance:
216 layout.operator("wm.return_to_original",
217 text="Reload Current File",
218 icon="FILE_REFRESH").use_autosave = False
220 layout.separator()
222 # XXX - This is for nested linked assets... but it only works
223 # when launching a new Blender instance. Nested links don't
224 # currently work when using a single instance of Blender.
225 props = layout.operator("object.edit_linked",
226 text="Edit Library: %s" % context.active_object.instance_collection.name,
227 icon="LINK_BLEND")
229 self.draw_common(scene, layout, props)
231 layout.label(text="Path: %s" %
232 context.active_object.instance_collection.library.filepath)
234 else:
235 props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
236 props.use_autosave = scene.use_autosave
238 layout.prop(scene, "use_autosave")
240 else:
241 layout.label(text="%s is not linked" % context.active_object.name,
242 icon=icon)
245 class TOPBAR_MT_edit_linked_submenu(bpy.types.Menu):
246 bl_label = 'Edit Linked Library'
248 def draw(self, context):
249 self.layout.separator()
250 self.layout.operator(OBJECT_OT_EditLinked.bl_idname)
251 self.layout.operator(WM_OT_ReturnToOriginal.bl_idname)
254 addon_keymaps = []
255 classes = (
256 OBJECT_OT_EditLinked,
257 WM_OT_ReturnToOriginal,
258 VIEW3D_PT_PanelLinkedEdit,
259 TOPBAR_MT_edit_linked_submenu
263 def register():
264 bpy.app.handlers.load_post.append(linked_file_check)
266 for c in classes:
267 bpy.utils.register_class(c)
269 bpy.types.Scene.use_autosave = bpy.props.BoolProperty(
270 name="Autosave",
271 description="Save the current file before opening a linked file",
272 default=True)
274 bpy.types.Scene.use_instance = bpy.props.BoolProperty(
275 name="New Blender Instance",
276 description="Open in a new Blender instance",
277 default=False)
279 # add the function to the file menu
280 bpy.types.TOPBAR_MT_file_external_data.append(TOPBAR_MT_edit_linked_submenu.draw)
282 # Keymapping (deactivated by default; activated when a library object is selected)
283 kc = bpy.context.window_manager.keyconfigs.addon
284 if kc: # don't register keymaps from command line
285 km = kc.keymaps.new(name="3D View", space_type='VIEW_3D')
286 kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True)
287 kmi.active = True
288 addon_keymaps.append((km, kmi))
289 kmi = km.keymap_items.new("wm.return_to_original", 'NUMPAD_SLASH', 'PRESS', shift=True)
290 kmi.active = True
291 addon_keymaps.append((km, kmi))
294 def unregister():
296 bpy.app.handlers.load_post.remove(linked_file_check)
297 bpy.types.TOPBAR_MT_file_external_data.remove(TOPBAR_MT_edit_linked_submenu)
299 del bpy.types.Scene.use_autosave
300 del bpy.types.Scene.use_instance
302 # handle the keymap
303 for km, kmi in addon_keymaps:
304 km.keymap_items.remove(kmi)
305 addon_keymaps.clear()
307 for c in reversed(classes):
308 bpy.utils.unregister_class(c)
311 if __name__ == "__main__":
312 register()