Pose Library: update for rename of asset_library to asset_library_ref
[blender-addons.git] / object_edit_linked.py
blob95b4ec528203dce253b0e26ebfc9c2c7de30f350
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, 2),
24 "blender": (2, 80, 0),
25 "location": "File > External Data / View3D > Sidebar > Item Tab / Node Editor > Sidebar > Node Tab",
26 "description": "Allows editing of objects, collections, and node groups linked from a .blend library.",
27 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/edit_linked_library.html",
28 "category": "Object",
31 import bpy
32 import logging
33 import os
35 from bpy.app.handlers import persistent
37 logger = logging.getLogger('object_edit_linked')
39 settings = {
40 "original_file": "",
41 "linked_file": "",
42 "linked_objects": [],
43 "linked_nodes": []
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 # Using both bpy and os abspath functions because Windows doesn't like relative routes as part of an absolute path
114 settings["linked_file"] = os.path.abspath(bpy.path.abspath(targetpath))
116 if self.use_instance:
117 import subprocess
118 try:
119 subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
120 except:
121 logger.error("Error on the new Blender instance")
122 import traceback
123 logger.error(traceback.print_exc())
124 else:
125 bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
127 logger.info("Opened linked file!")
128 else:
129 self.report({'WARNING'}, target.name + " is not linked")
130 logger.warning(target.name + " is not linked")
132 return {'FINISHED'}
135 class NODE_OT_EditLinked(bpy.types.Operator):
136 """Edit Linked Library"""
137 bl_idname = "node.edit_linked"
138 bl_label = "Edit Linked Library"
140 use_autosave: bpy.props.BoolProperty(
141 name="Autosave",
142 description="Save the current file before opening the linked library",
143 default=True)
144 use_instance: bpy.props.BoolProperty(
145 name="New Blender Instance",
146 description="Open in a new Blender instance",
147 default=False)
149 @classmethod
150 def poll(cls, context: bpy.context):
151 return settings["original_file"] == "" and context.active_node is not None and (
152 context.active_node.type == 'GROUP' and
153 hasattr(context.active_node.node_tree, "library") and
154 context.active_node.node_tree.library is not None)
156 def execute(self, context: bpy.context):
157 target = context.active_node.node_tree
159 targetpath = target.library.filepath
160 settings["linked_nodes"].append(target.name)
162 if targetpath:
163 logger.debug(target.name + " is linked to " + targetpath)
165 if self.use_autosave:
166 if not bpy.data.filepath:
167 # File is not saved on disk, better to abort!
168 self.report({'ERROR'}, "Current file does not exist on disk, we cannot autosave it, aborting")
169 return {'CANCELLED'}
170 bpy.ops.wm.save_mainfile()
172 settings["original_file"] = bpy.data.filepath
173 # Using both bpy and os abspath functions because Windows doesn't like relative routes as part of an absolute path
174 settings["linked_file"] = os.path.abspath(bpy.path.abspath(targetpath))
176 if self.use_instance:
177 import subprocess
178 try:
179 subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
180 except:
181 logger.error("Error on the new Blender instance")
182 import traceback
183 logger.error(traceback.print_exc())
184 else:
185 bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
187 logger.info("Opened linked file!")
188 else:
189 self.report({'WARNING'}, target.name + " is not linked")
190 logger.warning(target.name + " is not linked")
192 return {'FINISHED'}
195 class WM_OT_ReturnToOriginal(bpy.types.Operator):
196 """Load the original file"""
197 bl_idname = "wm.return_to_original"
198 bl_label = "Return to Original File"
200 use_autosave: bpy.props.BoolProperty(
201 name="Autosave",
202 description="Save the current file before opening original file",
203 default=True)
205 @classmethod
206 def poll(cls, context: bpy.context):
207 return (settings["original_file"] != "")
209 def execute(self, context: bpy.context):
210 if self.use_autosave:
211 bpy.ops.wm.save_mainfile()
213 bpy.ops.wm.open_mainfile(filepath=settings["original_file"])
215 settings["original_file"] = ""
216 settings["linked_objects"] = []
217 logger.info("Back to the original!")
218 return {'FINISHED'}
221 class VIEW3D_PT_PanelLinkedEdit(bpy.types.Panel):
222 bl_label = "Edit Linked Library"
223 bl_space_type = "VIEW_3D"
224 bl_region_type = 'UI'
225 bl_category = "Item"
226 bl_context = 'objectmode'
227 bl_options = {'DEFAULT_CLOSED'}
229 @classmethod
230 def poll(cls, context: bpy.context):
231 return (context.active_object is not None) or (settings["original_file"] != "")
233 def draw_common(self, scene, layout, props):
234 if props is not None:
235 props.use_autosave = scene.use_autosave
236 props.use_instance = scene.use_instance
238 layout.prop(scene, "use_autosave")
239 layout.prop(scene, "use_instance")
241 def draw(self, context: bpy.context):
242 scene = context.scene
243 layout = self.layout
244 layout.use_property_split = False
245 layout.use_property_decorate = False
246 icon = "OUTLINER_DATA_" + context.active_object.type.replace("LIGHT_PROBE", "LIGHTPROBE")
248 target = None
250 if context.active_object.proxy:
251 target = context.active_object.proxy
252 else:
253 target = context.active_object.instance_collection
255 if settings["original_file"] == "" and (
256 (target and
257 target.library is not None) or
258 context.active_object.library is not None):
260 if (target is not None):
261 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
262 text="Edit Library: %s" % target.name)
263 else:
264 props = layout.operator("object.edit_linked", icon="LINK_BLEND",
265 text="Edit Library: %s" % context.active_object.name)
267 self.draw_common(scene, layout, props)
269 if (target is not None):
270 layout.label(text="Path: %s" %
271 target.library.filepath)
272 else:
273 layout.label(text="Path: %s" %
274 context.active_object.library.filepath)
276 elif settings["original_file"] != "":
278 if scene.use_instance:
279 layout.operator("wm.return_to_original",
280 text="Reload Current File",
281 icon="FILE_REFRESH").use_autosave = False
283 layout.separator()
285 # XXX - This is for nested linked assets... but it only works
286 # when launching a new Blender instance. Nested links don't
287 # currently work when using a single instance of Blender.
288 if context.active_object.instance_collection is not None:
289 props = layout.operator("object.edit_linked",
290 text="Edit Library: %s" % context.active_object.instance_collection.name,
291 icon="LINK_BLEND")
292 else:
293 props = None
295 self.draw_common(scene, layout, props)
297 if context.active_object.instance_collection is not None:
298 layout.label(text="Path: %s" %
299 context.active_object.instance_collection.library.filepath)
301 else:
302 props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
303 props.use_autosave = scene.use_autosave
305 layout.prop(scene, "use_autosave")
307 else:
308 layout.label(text="%s is not linked" % context.active_object.name,
309 icon=icon)
312 class NODE_PT_PanelLinkedEdit(bpy.types.Panel):
313 bl_label = "Edit Linked Library"
314 bl_space_type = 'NODE_EDITOR'
315 bl_region_type = 'UI'
316 if bpy.app.version >= (2, 93, 0):
317 bl_category = "Node"
318 else:
319 bl_category = "Item"
320 bl_options = {'DEFAULT_CLOSED'}
322 @classmethod
323 def poll(cls, context):
324 return context.active_node is not None
326 def draw_common(self, scene, layout, props):
327 if props is not None:
328 props.use_autosave = scene.use_autosave
329 props.use_instance = scene.use_instance
331 layout.prop(scene, "use_autosave")
332 layout.prop(scene, "use_instance")
334 def draw(self, context):
335 scene = context.scene
336 layout = self.layout
337 layout.use_property_split = False
338 layout.use_property_decorate = False
339 icon = 'NODETREE'
341 target = context.active_node
343 if settings["original_file"] == "" and (
344 target.type == 'GROUP' and hasattr(target.node_tree, "library") and
345 target.node_tree.library is not None):
347 props = layout.operator("node.edit_linked", icon="LINK_BLEND",
348 text="Edit Library: %s" % target.name)
350 self.draw_common(scene, layout, props)
352 layout.label(text="Path: %s" % target.node_tree.library.filepath)
354 elif settings["original_file"] != "":
356 if scene.use_instance:
357 layout.operator("wm.return_to_original",
358 text="Reload Current File",
359 icon="FILE_REFRESH").use_autosave = False
361 layout.separator()
363 props = None
365 self.draw_common(scene, layout, props)
367 #layout.label(text="Path: %s" %
368 # context.active_object.instance_collection.library.filepath)
370 else:
371 props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
372 props.use_autosave = scene.use_autosave
374 layout.prop(scene, "use_autosave")
376 else:
377 layout.label(text="%s is not linked" % target.name, icon=icon)
380 class TOPBAR_MT_edit_linked_submenu(bpy.types.Menu):
381 bl_label = 'Edit Linked Library'
383 def draw(self, context):
384 self.layout.separator()
385 self.layout.operator(OBJECT_OT_EditLinked.bl_idname)
386 self.layout.operator(WM_OT_ReturnToOriginal.bl_idname)
389 addon_keymaps = []
390 classes = (
391 OBJECT_OT_EditLinked,
392 NODE_OT_EditLinked,
393 WM_OT_ReturnToOriginal,
394 VIEW3D_PT_PanelLinkedEdit,
395 NODE_PT_PanelLinkedEdit,
396 TOPBAR_MT_edit_linked_submenu
400 def register():
401 bpy.app.handlers.load_post.append(linked_file_check)
403 for c in classes:
404 bpy.utils.register_class(c)
406 bpy.types.Scene.use_autosave = bpy.props.BoolProperty(
407 name="Autosave",
408 description="Save the current file before opening a linked file",
409 default=True)
411 bpy.types.Scene.use_instance = bpy.props.BoolProperty(
412 name="New Blender Instance",
413 description="Open in a new Blender instance",
414 default=False)
416 # add the function to the file menu
417 bpy.types.TOPBAR_MT_file_external_data.append(TOPBAR_MT_edit_linked_submenu.draw)
422 def unregister():
424 bpy.app.handlers.load_post.remove(linked_file_check)
425 bpy.types.TOPBAR_MT_file_external_data.remove(TOPBAR_MT_edit_linked_submenu)
427 del bpy.types.Scene.use_autosave
428 del bpy.types.Scene.use_instance
431 for c in reversed(classes):
432 bpy.utils.unregister_class(c)
435 if __name__ == "__main__":
436 register()