Skip to content
Snippets Groups Projects
Commit aa2e8a2a authored by Rainer Trummer's avatar Rainer Trummer Committed by Jacques Lucke
Browse files

Port 'Edit Linked Library' addon to Blender 2.8

Differential Revision: https://developer.blender.org/D4070
parent 96d48401
Branches
Tags
No related merge requests found
...@@ -19,21 +19,24 @@ ...@@ -19,21 +19,24 @@
bl_info = { bl_info = {
"name": "Edit Linked Library", "name": "Edit Linked Library",
"author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez", "author": "Jason van Gumster (Fweeb), Bassam Kurdali, Pablo Vazquez, Rainer Trummer",
"version": (0, 8, 1), "version": (0, 9, 1),
"blender": (2, 74, 0), "blender": (2, 80, 0),
"location": "View3D > Toolshelf > Edit Linked Library", "location": "File > External Data > Edit Linked Library",
"description": "Allows editing of objects linked from a .blend library.", "description": "Allows editing of objects linked from a .blend library.",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Object/Edit_Linked_Library", "Scripts/Object/Edit_Linked_Library",
"category": "Object", "category": "Object",
} }
import bpy import bpy
from bpy.app.handlers import persistent import logging
import os import os
from bpy.app.handlers import persistent
logger = logging.getLogger('object_edit_linked')
settings = { settings = {
"original_file": "", "original_file": "",
"linked_file": "", "linked_file": "",
...@@ -42,15 +45,15 @@ settings = { ...@@ -42,15 +45,15 @@ settings = {
@persistent @persistent
def linked_file_check(context): def linked_file_check(context: bpy.context):
if settings["linked_file"] != "": if settings["linked_file"] != "":
if os.path.samefile(settings["linked_file"], bpy.data.filepath): if os.path.samefile(settings["linked_file"], bpy.data.filepath):
print("Editing a linked library.") logger.info("Editing a linked library.")
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
for ob_name in settings["linked_objects"]: for ob_name in settings["linked_objects"]:
bpy.data.objects[ob_name].select = True # XXX Assumes selected object is in the active scene bpy.data.objects[ob_name].select_set(True) # XXX Assumes selected object is in the active scene
if len(settings["linked_objects"]) == 1: if len(settings["linked_objects"]) == 1:
bpy.context.scene.objects.active = bpy.data.objects[settings["linked_objects"][0]] context.view_layer.objects.active = bpy.data.objects[settings["linked_objects"][0]]
else: else:
# For some reason, the linked editing session ended # For some reason, the linked editing session ended
# (failed to find a file or opened a different file # (failed to find a file or opened a different file
...@@ -59,32 +62,30 @@ def linked_file_check(context): ...@@ -59,32 +62,30 @@ def linked_file_check(context):
settings["linked_file"] = "" settings["linked_file"] = ""
class EditLinked(bpy.types.Operator): class OBJECT_OT_EditLinked(bpy.types.Operator):
"""Edit Linked Library""" """Edit Linked Library"""
bl_idname = "object.edit_linked" bl_idname = "object.edit_linked"
bl_label = "Edit Linked Library" bl_label = "Edit Linked Library"
use_autosave = bpy.props.BoolProperty( use_autosave: bpy.props.BoolProperty(
name="Autosave", name="Autosave",
description="Save the current file before opening the linked library", description="Save the current file before opening the linked library",
default=True) default=True)
use_instance = bpy.props.BoolProperty( use_instance: bpy.props.BoolProperty(
name="New Blender Instance", name="New Blender Instance",
description="Open in a new Blender instance", description="Open in a new Blender instance",
default=False) default=False)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context: bpy.context):
return settings["original_file"] == "" and context.active_object is not None and ( return settings["original_file"] == "" and context.active_object is not None and (
(context.active_object.instance_collection and (context.active_object.instance_collection and
context.active_object.instance_collection.library is not None) or context.active_object.instance_collection.library is not None) or
(context.active_object.proxy and (context.active_object.proxy and
context.active_object.proxy.library is not None) or context.active_object.proxy.library is not None) or
context.active_object.library is not None) context.active_object.library is not None)
#return context.active_object is not None
def execute(self, context): def execute(self, context: bpy.context):
#print(bpy.context.active_object.library)
target = context.active_object target = context.active_object
if target.instance_collection and target.instance_collection.library: if target.instance_collection and target.instance_collection.library:
...@@ -99,7 +100,7 @@ class EditLinked(bpy.types.Operator): ...@@ -99,7 +100,7 @@ class EditLinked(bpy.types.Operator):
settings["linked_objects"].append(target.name) settings["linked_objects"].append(target.name)
if targetpath: if targetpath:
print(target.name + " is linked to " + targetpath) logger.debug(target.name + " is linked to " + targetpath)
if self.use_autosave: if self.use_autosave:
if not bpy.data.filepath: if not bpy.data.filepath:
...@@ -116,35 +117,35 @@ class EditLinked(bpy.types.Operator): ...@@ -116,35 +117,35 @@ class EditLinked(bpy.types.Operator):
try: try:
subprocess.Popen([bpy.app.binary_path, settings["linked_file"]]) subprocess.Popen([bpy.app.binary_path, settings["linked_file"]])
except: except:
print("Error on the new Blender instance") logger.error("Error on the new Blender instance")
import traceback import traceback
traceback.print_exc() logger.error(traceback.print_exc())
else: else:
bpy.ops.wm.open_mainfile(filepath=settings["linked_file"]) bpy.ops.wm.open_mainfile(filepath=settings["linked_file"])
print("Opened linked file!") logger.info("Opened linked file!")
else: else:
self.report({'WARNING'}, target.name + " is not linked") self.report({'WARNING'}, target.name + " is not linked")
print(target.name + " is not linked") logger.warning(target.name + " is not linked")
return {'FINISHED'} return {'FINISHED'}
class ReturnToOriginal(bpy.types.Operator): class WM_OT_ReturnToOriginal(bpy.types.Operator):
"""Load the original file""" """Load the original file"""
bl_idname = "wm.return_to_original" bl_idname = "wm.return_to_original"
bl_label = "Return to Original File" bl_label = "Return to Original File"
use_autosave = bpy.props.BoolProperty( use_autosave: bpy.props.BoolProperty(
name="Autosave", name="Autosave",
description="Save the current file before opening original file", description="Save the current file before opening original file",
default=True) default=True)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context: bpy.context):
return (settings["original_file"] != "") return (settings["original_file"] != "")
def execute(self, context): def execute(self, context: bpy.context):
if self.use_autosave: if self.use_autosave:
bpy.ops.wm.save_mainfile() bpy.ops.wm.save_mainfile()
...@@ -152,25 +153,29 @@ class ReturnToOriginal(bpy.types.Operator): ...@@ -152,25 +153,29 @@ class ReturnToOriginal(bpy.types.Operator):
settings["original_file"] = "" settings["original_file"] = ""
settings["linked_objects"] = [] settings["linked_objects"] = []
print("Back to the original!") logger.info("Back to the original!")
return {'FINISHED'} return {'FINISHED'}
# UI class VIEW3D_PT_PanelLinkedEdit(bpy.types.Panel):
# TODO:Add operators to the File menu?
# Hide the entire panel for non-linked objects?
class PanelLinkedEdit(bpy.types.Panel):
bl_label = "Edit Linked Library" bl_label = "Edit Linked Library"
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS" bl_region_type = 'UI'
bl_category = "Relations" bl_category = "View"
bl_context = "objectmode" bl_context = 'objectmode'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context: bpy.context):
return (context.active_object is not None) or (settings["original_file"] != "") return (context.active_object is not None) or (settings["original_file"] != "")
def draw(self, context): def draw_common(self, scene, layout, props):
props.use_autosave = scene.use_autosave
props.use_instance = scene.use_instance
layout.prop(scene, "use_autosave")
layout.prop(scene, "use_instance")
def draw(self, context: bpy.context):
layout = self.layout layout = self.layout
scene = context.scene scene = context.scene
icon = "OUTLINER_DATA_" + context.active_object.type icon = "OUTLINER_DATA_" + context.active_object.type
...@@ -184,7 +189,7 @@ class PanelLinkedEdit(bpy.types.Panel): ...@@ -184,7 +189,7 @@ class PanelLinkedEdit(bpy.types.Panel):
if settings["original_file"] == "" and ( if settings["original_file"] == "" and (
(target and (target and
target.library is not None) or target.library is not None) or
context.active_object.library is not None): context.active_object.library is not None):
if (target is not None): if (target is not None):
...@@ -193,18 +198,15 @@ class PanelLinkedEdit(bpy.types.Panel): ...@@ -193,18 +198,15 @@ class PanelLinkedEdit(bpy.types.Panel):
else: else:
props = layout.operator("object.edit_linked", icon="LINK_BLEND", props = layout.operator("object.edit_linked", icon="LINK_BLEND",
text="Edit Library: %s" % context.active_object.name) text="Edit Library: %s" % context.active_object.name)
props.use_autosave = scene.use_autosave
props.use_instance = scene.use_instance
layout.prop(scene, "use_autosave") self.draw_common(scene, layout, props)
layout.prop(scene, "use_instance")
if (target is not None): if (target is not None):
layout.label(text="Path: %s" % layout.label(text="Path: %s" %
target.library.filepath) target.library.filepath)
else: else:
layout.label(text="Path: %s" % layout.label(text="Path: %s" %
context.active_object.library.filepath) context.active_object.library.filepath)
elif settings["original_file"] != "": elif settings["original_file"] != "":
...@@ -215,19 +217,17 @@ class PanelLinkedEdit(bpy.types.Panel): ...@@ -215,19 +217,17 @@ class PanelLinkedEdit(bpy.types.Panel):
layout.separator() layout.separator()
#XXX - This is for nested linked assets... but it only works # XXX - This is for nested linked assets... but it only works
# when launching a new Blender instance. Nested links don't # when launching a new Blender instance. Nested links don't
# currently work when using a single instance of Blender. # currently work when using a single instance of Blender.
props = layout.operator("object.edit_linked", props = layout.operator("object.edit_linked",
text="Edit Library: %s" % context.active_object.instance_collection.name, text="Edit Library: %s" % context.active_object.instance_collection.name,
icon="LINK_BLEND") icon="LINK_BLEND")
props.use_autosave = scene.use_autosave
props.use_instance = scene.use_instance self.draw_common(scene, layout, props)
layout.prop(scene, "use_autosave")
layout.prop(scene, "use_instance")
layout.label(text="Path: %s" % layout.label(text="Path: %s" %
context.active_object.instance_collection.library.filepath) context.active_object.instance_collection.library.filepath)
else: else:
props = layout.operator("wm.return_to_original", icon="LOOP_BACK") props = layout.operator("wm.return_to_original", icon="LOOP_BACK")
...@@ -237,31 +237,50 @@ class PanelLinkedEdit(bpy.types.Panel): ...@@ -237,31 +237,50 @@ class PanelLinkedEdit(bpy.types.Panel):
else: else:
layout.label(text="%s is not linked" % context.active_object.name, layout.label(text="%s is not linked" % context.active_object.name,
icon=icon) icon=icon)
class TOPBAR_MT_edit_linked_submenu(bpy.types.Menu):
bl_label = 'Edit Linked Library'
bl_idname = 'view3d.TOPBAR_MT_edit_linked_submenu'
def draw(self, context):
self.layout.separator()
self.layout.operator(OBJECT_OT_EditLinked.bl_idname)
self.layout.operator(WM_OT_ReturnToOriginal.bl_idname)
addon_keymaps = [] addon_keymaps = []
classes = (
OBJECT_OT_EditLinked,
WM_OT_ReturnToOriginal,
VIEW3D_PT_PanelLinkedEdit,
TOPBAR_MT_edit_linked_submenu
)
def register(): def register():
bpy.app.handlers.load_post.append(linked_file_check) bpy.app.handlers.load_post.append(linked_file_check)
bpy.utils.register_class(EditLinked)
bpy.utils.register_class(ReturnToOriginal) for c in classes:
bpy.utils.register_class(PanelLinkedEdit) bpy.utils.register_class(c)
# Is there a better place to store this properties?
bpy.types.Scene.use_autosave = bpy.props.BoolProperty( bpy.types.Scene.use_autosave = bpy.props.BoolProperty(
name="Autosave", name="Autosave",
description="Save the current file before opening a linked file", description="Save the current file before opening a linked file",
default=True) default=True)
bpy.types.Scene.use_instance = bpy.props.BoolProperty( bpy.types.Scene.use_instance = bpy.props.BoolProperty(
name="New Blender Instance", name="New Blender Instance",
description="Open in a new Blender instance", description="Open in a new Blender instance",
default=False) default=False)
# add the function to the file menu
bpy.types.TOPBAR_MT_file_external_data.append(TOPBAR_MT_edit_linked_submenu.draw)
# Keymapping (deactivated by default; activated when a library object is selected) # Keymapping (deactivated by default; activated when a library object is selected)
kc = bpy.context.window_manager.keyconfigs.addon kc = bpy.context.window_manager.keyconfigs.addon
if kc: # don't register keymaps from command line if kc: # don't register keymaps from command line
km = kc.keymaps.new(name="3D View", space_type='VIEW_3D') km = kc.keymaps.new(name="3D View", space_type='VIEW_3D')
kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True) kmi = km.keymap_items.new("object.edit_linked", 'NUMPAD_SLASH', 'PRESS', shift=True)
kmi.active = True kmi.active = True
...@@ -272,10 +291,9 @@ def register(): ...@@ -272,10 +291,9 @@ def register():
def unregister(): def unregister():
bpy.utils.unregister_class(EditLinked)
bpy.utils.unregister_class(ReturnToOriginal) bpy.app.handlers.load_post.remove(linked_file_check)
bpy.utils.unregister_class(PanelLinkedEdit) bpy.types.TOPBAR_MT_file_external_data.remove(TOPBAR_MT_edit_linked_submenu)
bpy.app.handlers.load_post.remove(linked_file_check)
del bpy.types.Scene.use_autosave del bpy.types.Scene.use_autosave
del bpy.types.Scene.use_instance del bpy.types.Scene.use_instance
...@@ -285,6 +303,9 @@ def unregister(): ...@@ -285,6 +303,9 @@ def unregister():
km.keymap_items.remove(kmi) km.keymap_items.remove(kmi)
addon_keymaps.clear() addon_keymaps.clear()
for c in reversed(classes):
bpy.utils.unregister_class(c)
if __name__ == "__main__": if __name__ == "__main__":
register() register()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment