feat(Blender): a few QOL improvements to the workflow

* context menu + shortcut + operators to quickly edit /and/or create new blueprints in a seperate scene
 * boilerplate based on the awesome work by slyedoc
 * "one keyboard shortcut" workflow: edit if blueprint instance is selected, create if not, stop editing if editing is already in progress
 * updated list of contributors
This commit is contained in:
kaosat.dev 2024-08-14 01:51:17 +02:00
parent 7e71e30187
commit 566120d073
6 changed files with 295 additions and 25 deletions

View File

@ -112,6 +112,8 @@ Thanks to all the contributors helping out with this project ! Big kudos to you,
* [killercup](https://github.com/killercup) * [killercup](https://github.com/killercup)
* [janhohenheim](https://github.com/janhohenheim) * [janhohenheim](https://github.com/janhohenheim)
* [BUGO07](https://github.com/BUGO07) * [BUGO07](https://github.com/BUGO07)
* [ChristopherBiscardi](https://github.com/ChristopherBiscardi)
* [slyedoc](https://github.com/slyedoc)
## License ## License

View File

@ -52,6 +52,7 @@ from .core.blenvy_manager import BlenvyManager
from .core.operators import BLENVY_OT_configuration_switch, BLENVY_OT_tooling_switch from .core.operators import BLENVY_OT_configuration_switch, BLENVY_OT_tooling_switch
from .core.ui.ui import (BLENVY_PT_SidePanel) from .core.ui.ui import (BLENVY_PT_SidePanel)
from .core.ui.scenes_list import BLENVY_OT_scenes_list_actions from .core.ui.scenes_list import BLENVY_OT_scenes_list_actions
from .core.ui.menus_and_shortcuts import BLENVY_OT_ui_blueprint_create, BLENVY_OT_ui_blueprint_edit_start, BLENVY_OT_ui_blueprint_edit_end, BLENVY_OT_ui_blueprint_create_or_edit, edit_or_create_blueprint_menu
from .assets.assets_folder_browser import BLENVY_OT_assets_paths_browse from .assets.assets_folder_browser import BLENVY_OT_assets_paths_browse
@ -126,6 +127,11 @@ classes = [
BlueprintsRegistry, BlueprintsRegistry,
BLENVY_OT_blueprint_select, BLENVY_OT_blueprint_select,
BLENVY_PT_blueprints_panel, BLENVY_PT_blueprints_panel,
BLENVY_OT_ui_blueprint_create,
BLENVY_OT_ui_blueprint_edit_start,
BLENVY_OT_ui_blueprint_edit_end,
BLENVY_OT_ui_blueprint_create_or_edit
] ]
@ -139,40 +145,37 @@ def post_save(scene, depsgraph):
@persistent @persistent
def post_load(file_name): def post_load(file_name):
print("POST LOAD")
blenvy = bpy.context.window_manager.blenvy blenvy = bpy.context.window_manager.blenvy
if blenvy is not None: if blenvy is not None:
blenvy.load_settings() blenvy.load_settings()
def init_keymaps():
window_manager = bpy.context.window_manager
if window_manager.keyconfigs.addon:
km = window_manager.keyconfigs.addon.keymaps.new(name='3D View', space_type='VIEW_3D')
kmi = [
km.keymap_items.new(BLENVY_OT_ui_blueprint_create_or_edit.bl_idname, "F", "PRESS", shift=True),
]
return km, kmi
addon_keymaps = []
def register(): def register():
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
bpy.app.handlers.load_post.append(post_load) bpy.app.handlers.load_post.append(post_load)
# for some reason, adding these directly to the tracker class in register() do not work reliably # for some reason, adding these directly to the tracker class in register() do not work reliably
bpy.app.handlers.depsgraph_update_post.append(post_update) bpy.app.handlers.depsgraph_update_post.append(post_update)
bpy.app.handlers.save_post.append(post_save) bpy.app.handlers.save_post.append(post_save)
bpy.types.VIEW3D_MT_object.append(edit_or_create_blueprint_menu)
bpy.types.VIEW3D_MT_object_context_menu.append(edit_or_create_blueprint_menu)
""" handle = object() if not bpy.app.background:
km, kmi = init_keymaps()
subscribe_to = bpy.types.Scene, "name" # for k in kmi:
k.active = True
def notify_test(context): addon_keymaps.append((km, k))
#if (context.scene.type == 'MESH'):
print("Renamed", dir(context), context.scenes)
bpy.msgbus.subscribe_rna(
key=subscribe_to,
owner=bpy,
args=(bpy.context,),
notify=notify_test,
)"""
#bpy.msgbus.publish_rna(key=subscribe_to)
def unregister(): def unregister():
@ -181,3 +184,12 @@ def unregister():
bpy.app.handlers.load_post.remove(post_load) bpy.app.handlers.load_post.remove(post_load)
bpy.app.handlers.depsgraph_update_post.remove(post_update) bpy.app.handlers.depsgraph_update_post.remove(post_update)
bpy.app.handlers.save_post.remove(post_save) bpy.app.handlers.save_post.remove(post_save)
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
bpy.types.VIEW3D_MT_object.remove(edit_or_create_blueprint_menu)
bpy.types.VIEW3D_MT_object_context_menu.remove(edit_or_create_blueprint_menu)

View File

@ -34,7 +34,6 @@ def get_object_scene(object):
return scenes_of_object[0] return scenes_of_object[0]
return None return None
def get_mesh_object(mesh): def get_mesh_object(mesh):
for object in bpy.data.objects: for object in bpy.data.objects:
if isinstance(object.data, bpy.types.Mesh) and mesh.name == object.data.name: if isinstance(object.data, bpy.types.Mesh) and mesh.name == object.data.name:
@ -69,6 +68,12 @@ class BLENVY_OT_item_select(Operator):
description="target to select's name ", description="target to select's name ",
) # type: ignore ) # type: ignore
override_scene_name: StringProperty(
name="override scene name",
description="use this to override the scene selection mecanism",
default=""
) # type: ignore
@classmethod @classmethod
def register(cls): def register(cls):
@ -96,8 +101,9 @@ class BLENVY_OT_item_select(Operator):
select_area(context=context, area_name="OBJECT") select_area(context=context, area_name="OBJECT")
elif self.item_type == 'COLLECTION': elif self.item_type == 'COLLECTION':
print("selecting collection")
collection = bpy.data.collections[self.target_name] collection = bpy.data.collections[self.target_name]
scene_of_collection = get_collection_scene(collection) scene_of_collection = get_collection_scene(collection) if self.override_scene_name == "" else bpy.data.scenes.get(self.override_scene_name, None)
if scene_of_collection is not None: if scene_of_collection is not None:
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.context.window.scene = scene_of_collection bpy.context.window.scene = scene_of_collection
@ -105,7 +111,7 @@ class BLENVY_OT_item_select(Operator):
context.window_manager.blenvy_item_selected_ids = json.dumps({"name": collection.name, "type": self.item_type}) context.window_manager.blenvy_item_selected_ids = json.dumps({"name": collection.name, "type": self.item_type})
set_active_collection(bpy.context.window.scene, collection.name) set_active_collection(bpy.context.window.scene, collection.name)
select_area(context=context, area_name="COLLECTION") #select_area(context=context, area_name="COLLECTION")
elif self.item_type == 'MESH': elif self.item_type == 'MESH':
mesh = bpy.data.meshes[self.target_name] mesh = bpy.data.meshes[self.target_name]

View File

@ -137,6 +137,28 @@ class BlenvyManager(PropertyGroup):
get=lambda self: os.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), self.project_root_path, self.assets_path, self.animations_path)) get=lambda self: os.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), self.project_root_path, self.assets_path, self.animations_path))
) # type: ignore ) # type: ignore
# Edit blueprint settings
edit_blueprint_previous_scene: StringProperty(
name="edit_blueprint_previous_scene",
description="name of the scene we started from for editing the current Blueprint",
default="",
update=save_settings
)# type: ignore
edit_blueprint_current_scene: StringProperty(
name="edit_blueprint_current_scene",
description="name of the scene where we are currently editing a Blueprint in",
default="",
update=save_settings
)# type: ignore
edit_blueprint_previous_mode: StringProperty(
name="edit_blueprint_previous_mode",
description="previous blenvy mode before starting editing the current blueprint",
default="",
update=save_settings
)# type: ignore
# sub ones # sub ones
auto_export: PointerProperty(type=AutoExportSettings) # type: ignore auto_export: PointerProperty(type=AutoExportSettings) # type: ignore
components: PointerProperty(type=ComponentsSettings) # type: ignore components: PointerProperty(type=ComponentsSettings) # type: ignore

View File

@ -17,7 +17,10 @@ def recurLayerCollection(layerColl, collName):
return found return found
def set_active_collection(scene, collection_name): def set_active_collection(scene, collection_name):
layer_collection = bpy.data.scenes[scene.name].view_layers['ViewLayer'].layer_collection print("set active collection", scene, collection_name)
layer_collection = scene.view_layers['ViewLayer'].layer_collection
layerColl = recurLayerCollection(layer_collection, collection_name) layerColl = recurLayerCollection(layer_collection, collection_name)
print("layerColl", layerColl)
# set active collection to the collection # set active collection to the collection
bpy.context.view_layer.active_layer_collection = layerColl bpy.context.view_layer.active_layer_collection = layerColl

View File

@ -0,0 +1,225 @@
import bpy
import json
from mathutils import Vector
from ..helpers_collections import set_active_collection
from ..blenvy_manager import BlenvyManager
""" This file contains quality of life operators/menus/shortcuts to make working with blueprints more pleasant
* based on the excellent work by slyedoc: https://github.com/slyedoc/bevy_sly_blender/tree/4223cc0ff86255f82bb555ffc8eddf65e91aa636
- [ ] detect editing in progress
- [x] select collection instead of objects
- [ ] if current scene (before edit)
- is library: do not create instance
- is main scene: create instance
- or alternative: sub menu to choose instance creation or not
- [x] save & restore blenvy mode
- [x] add a contextual shortcut to easilly jump in/out of editing mode
- [ ] save & reset camera
"""
def edit_or_create_blueprint_menu(self, context):
if bpy.context.active_object and bpy.context.active_object.instance_collection:
self.layout.operator(BLENVY_OT_ui_blueprint_edit_start.bl_idname)
else:
blenvy = context.window_manager.blenvy # type: BlenvyManager
prev_scene = bpy.data.scenes.get(blenvy.edit_blueprint_previous_scene)
if prev_scene is not None:
self.layout.operator(BLENVY_OT_ui_blueprint_edit_end.bl_idname)
else:
self.layout.operator(BLENVY_OT_ui_blueprint_create.bl_idname)
class BLENVY_OT_ui_blueprint_create_or_edit(bpy.types.Operator):
"""Create Blueprint in a new Scene"""
bl_idname = "window_manager.blenvy_blueprint_shortcut"
bl_label = "Edit Blueprint"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blenvy = context.window_manager.blenvy # type: BlenvyManager
if bpy.context.active_object and bpy.context.active_object.instance_collection:
bpy.ops.window_manager.blenvy_edit_blueprinrt()
else:
blenvy = context.window_manager.blenvy # type: BlenvyManager
prev_scene = bpy.data.scenes.get(blenvy.edit_blueprint_previous_scene)
if prev_scene is not None:
bpy.ops.window_manager.blenvy_exit_edit_blueprint()
else:
bpy.ops.window_manager.blenvy_create_blueprint()
return {"FINISHED"}
class BLENVY_OT_ui_blueprint_create(bpy.types.Operator):
"""Create Blueprint in a new Scene"""
bl_idname = "window_manager.blenvy_create_blueprint"
bl_label = "Create Blueprint"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blenvy = context.window_manager.blenvy # type: BlenvyManager
# Store original Blenvy setting
blenvy.edit_blueprint_previous_scene = bpy.context.scene.name
blenvy.edit_blueprint_previous_mode = blenvy.mode
# set mode to components
blenvy.mode = "COMPONENTS"
blueprint_name = "new_blueprint"
collection = bpy.data.collections.new(blueprint_name)
library_scene_name = "Library"
if len(blenvy.library_scenes_names) > 0:
library_scene_name = blenvy.library_scenes_names[0]
else:
bpy.data.scenes.new(library_scene_name)
# automatically add it to the library : find library scene, if any, if not, create it
bpy.data.scenes[library_scene_name].collection.children.link(collection)
# create an instance of the
source_collection = collection
instance_obj = bpy.data.objects.new(
name=collection.name,
object_data=None
)
instance_obj.instance_collection = source_collection
instance_obj.instance_type = 'COLLECTION'
parent_collection = bpy.context.view_layer.active_layer_collection
parent_collection.collection.objects.link(instance_obj)
# now open the temporary scene
scene_name = f"temp:{blueprint_name}"
bpy.ops.scene.new(type="EMPTY")
new_scene = bpy.context.scene
new_scene.name = scene_name
bpy.context.window.scene = new_scene
new_scene.collection.children.link(collection)
# deselect all objects then select the first object in new scene
bpy.ops.object.select_all(action='DESELECT')
# zoom to selected
bpy.ops.view3d.view_selected()
# now that the 3d view has been adapted, we select what we actually need: the collection/blueprint
bpy.ops.blenvy.select_item(target_name=collection.name, item_type="COLLECTION", override_scene_name=scene_name)
return {"FINISHED"}
class BLENVY_OT_ui_blueprint_edit_start(bpy.types.Operator):
"""Edit the Blueprint referenced by this Blueprint Instance in a new Scene"""
bl_idname = "window_manager.blenvy_edit_blueprinrt"
bl_label = "Start Editing Blueprint"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
blenvy = context.window_manager.blenvy # type: BlenvyManager
collection = bpy.context.active_object.instance_collection
# Store original Blenvy setting
blenvy.edit_blueprint_previous_scene = bpy.context.scene.name
blenvy.edit_blueprint_previous_mode = blenvy.mode
# set mode to components
blenvy.mode = "COMPONENTS"
if not collection:
print("Active item is not a Blueprint/Collection instance")
self.report({"WARNING"}, "Active item is not a Blueprint/Collection instance")
return {"CANCELLED"}
scene_name = f"temp:{collection.name}"
bpy.ops.scene.new(type="EMPTY")
new_scene = bpy.context.scene
new_scene.name = scene_name
bpy.context.window.scene = new_scene
new_scene.collection.children.link(collection)
# Assuming you want to focus on the objects from the linked collection
# Switch to the new scene context
"""blenvy.edit_collection_world_texture = "checker"
if blenvy.edit_collection_world_texture != "none":
world = bpy.data.worlds.new(bpy.context.scene.name)
new_scene.world = world
world.use_nodes = True
tree = world.node_tree
if blenvy.edit_collection_world_texture in ["checker", "checker_view"]:
checker_texture = tree.nodes.new("ShaderNodeTexChecker")
checker_texture.inputs["Scale"].default_value = 20
checker_texture.location = Vector((-250, 0))
if blenvy.edit_collection_world_texture == "checker_view":
coord = tree.nodes.new("ShaderNodeTexCoord")
coord.location = Vector((-500, 0))
for op in coord.outputs:
op.hide = True
tree.links.new(coord.outputs["Window"], checker_texture.inputs["Vector"])
tree.links.new(checker_texture.outputs["Color"], tree.nodes["Background"].inputs["Color"])
elif blenvy.edit_collection_world_texture == "gray":
tree.nodes["Background"].inputs["Color"].default_value = (.3, .3, .3, 1)"""
# deselect all objects then select the first object in new scene
bpy.ops.object.select_all(action='DESELECT')
# find the root object
if len(collection.objects) > 0 :
root_obj = collection.objects[0]
while root_obj.parent:
root_obj = root_obj.parent
# select object and children
new_scene.objects[root_obj.name].select_set(True)
# def select_children(parent):
# for child in parent.children:
# child.select_set(True)
# select_children(child) # Recursively select further descendants
# select_children(root_obj);
# Select the view layer and view the selected objects
bpy.context.view_layer.objects.active = new_scene.objects[root_obj.name]
bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[collection.name]
# zoom to selected
bpy.ops.view3d.view_selected()
# now that the 3d view has been adapted, we select what we actually need: the collection/blueprint
bpy.ops.blenvy.select_item(target_name=collection.name, item_type="COLLECTION", override_scene_name=scene_name)
return {"FINISHED"}
class BLENVY_OT_ui_blueprint_edit_end(bpy.types.Operator):
bl_idname = "window_manager.blenvy_exit_edit_blueprint"
bl_label = "Done editing blueprint"
bl_options = {"UNDO"}
def execute(self, context):
blenvy = context.window_manager.blenvy # type: BlenvyManager
# = context.window_manager.bevy # type: BlenvyManager
current_scene = bpy.context.scene
prev_scene = bpy.data.scenes.get(blenvy.edit_blueprint_previous_scene)
# we are done editing the blueprint, reset settings to the way they were before
blenvy.edit_blueprint_previous_scene = ""
blenvy.mode = blenvy.edit_blueprint_previous_mode
if prev_scene is None:
print("No scene to return to")
return {'CANCELLED'}
if current_scene.name.startswith("temp:"):
bpy.data.scenes.remove(bpy.context.scene)
bpy.context.window.scene = prev_scene
else:
#if
print("Not in temp scene")
return {'CANCELLED'}
return {'FINISHED'}