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 *****
21 "name": "Edit Linked Library",
22 "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez, Rainer Trummer",
24 "blender": (2, 80, 0),
25 "location": "File > External Data / View3D > Sidebar > Item Tab",
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",
36 from bpy
.app
.handlers
import persistent
38 logger
= logging
.getLogger('object_edit_linked')
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]]
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(
72 description
="Save the current file before opening the linked library",
74 use_instance
: bpy
.props
.BoolProperty(
75 name
="New Blender Instance",
76 description
="Open in a new Blender instance",
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
})
95 targetpath
= target
.library
.filepath
96 settings
["linked_objects"].append(target
.name
)
99 targetpath
= target
.library
.filepath
100 settings
["linked_objects"].append(target
.name
)
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")
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
:
118 subprocess
.Popen([bpy
.app
.binary_path
, settings
["linked_file"]])
120 logger
.error("Error on the new Blender instance")
122 logger
.error(traceback
.print_exc())
124 bpy
.ops
.wm
.open_mainfile(filepath
=settings
["linked_file"])
126 logger
.info("Opened linked file!")
128 self
.report({'WARNING'}, target
.name
+ " is not linked")
129 logger
.warning(target
.name
+ " is not linked")
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(
141 description
="Save the current file before opening original file",
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!")
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'
165 bl_context
= 'objectmode'
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
181 layout
.use_property_split
= True
182 layout
.use_property_decorate
= False
183 icon
= "OUTLINER_DATA_" + context
.active_object
.type
187 if context
.active_object
.proxy
:
188 target
= context
.active_object
.proxy
190 target
= context
.active_object
.instance_collection
192 if settings
["original_file"] == "" 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
)
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
)
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
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
,
229 self
.draw_common(scene
, layout
, props
)
231 layout
.label(text
="Path: %s" %
232 context
.active_object
.instance_collection
.library
.filepath
)
235 props
= layout
.operator("wm.return_to_original", icon
="LOOP_BACK")
236 props
.use_autosave
= scene
.use_autosave
238 layout
.prop(scene
, "use_autosave")
241 layout
.label(text
="%s is not linked" % context
.active_object
.name
,
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
)
256 OBJECT_OT_EditLinked
,
257 WM_OT_ReturnToOriginal
,
258 VIEW3D_PT_PanelLinkedEdit
,
259 TOPBAR_MT_edit_linked_submenu
264 bpy
.app
.handlers
.load_post
.append(linked_file_check
)
267 bpy
.utils
.register_class(c
)
269 bpy
.types
.Scene
.use_autosave
= bpy
.props
.BoolProperty(
271 description
="Save the current file before opening a linked file",
274 bpy
.types
.Scene
.use_instance
= bpy
.props
.BoolProperty(
275 name
="New Blender Instance",
276 description
="Open in a new Blender instance",
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)
288 addon_keymaps
.append((km
, kmi
))
289 kmi
= km
.keymap_items
.new("wm.return_to_original", 'NUMPAD_SLASH', 'PRESS', shift
=True)
291 addon_keymaps
.append((km
, kmi
))
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
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__":