Compare commits

..

2 Commits

Author SHA1 Message Date
kaosat.dev
f9cb6de4bc feat(Blenvy): added "levels" tab (likely going to be replacing the "assets" tab)
* added basic logic, ui, level selector operator etc
 * fixed issues with asset dialog
 * added experimental "always_export" flags for collections & scenes to enable always
exporting on save for select blueprints & levels (no logic yet, just UI)
 * various tweaks & minor experiments
2024-06-04 23:16:16 +02:00
kaosat.dev
2b6e17a6b7 feat(Blenvy): improvements to change detection & co
* node trees are now using the same logic as other types
 * cleaned up & restructured code accordingly
 * added more failure handling for project serialization & diffing
 * experimenting with ways to deal with scene renaming
 * minor tweaks
2024-06-04 14:37:29 +02:00
14 changed files with 275 additions and 97 deletions

View File

@ -74,7 +74,7 @@ Components:
General things to solve: General things to solve:
- [x] save settings - [x] save settings
- [x] load settings - [x] load settings
- [ ] add blueprints data - [x] add blueprints data
- [x] rename all path stuff using the old naming convention : "blueprints_path_full" - [x] rename all path stuff using the old naming convention : "blueprints_path_full"
- [x] generate the full paths directly when setting them in the UI - [x] generate the full paths directly when setting them in the UI
@ -102,6 +102,10 @@ General issues:
- [ ] add tests for disabled components - [ ] add tests for disabled components
- [x] fix auto export workflow - [x] fix auto export workflow
- [ ] should we write the previous _xxx data only AFTER a sucessfull export only ? - [ ] should we write the previous _xxx data only AFTER a sucessfull export only ?
- [ ] add hashing of modifiers/ geometry nodes in serialize scene - [x] add hashing of modifiers/ geometry nodes in serialize scene
- [ ] add ability to FORCE export specific blueprints & levels - [ ] add ability to FORCE export specific blueprints & levels
- [ ] undo after a save removes any saved "serialized scene" data ? DIG into this - [ ] undo after a save removes any saved "serialized scene" data ? DIG into this
- [ ] handle scene renames between saves (breaks diffing)
- [ ] change scene selector to work on actual scenes aka to deal with renamed scenes
- [x] fix asset file selection
- [x] change "assets" tab to "levels"/worlds tab & modify UI accordingly

View File

@ -38,6 +38,10 @@ from .assets.ui import Blenvy_assets
from .assets.assets_registry import Asset, AssetsRegistry from .assets.assets_registry import Asset, AssetsRegistry
from .assets.operators import OT_Add_asset_filebrowser, OT_add_bevy_asset, OT_remove_bevy_asset, OT_test_bevy_assets from .assets.operators import OT_Add_asset_filebrowser, OT_add_bevy_asset, OT_remove_bevy_asset, OT_test_bevy_assets
# levels management
from .levels.ui import Blenvy_levels
from .levels.operators import OT_select_level
# blueprints management # blueprints management
from .blueprints.ui import GLTF_PT_auto_export_blueprints_list from .blueprints.ui import GLTF_PT_auto_export_blueprints_list
from .blueprints.blueprints_registry import BlueprintsRegistry from .blueprints.blueprints_registry import BlueprintsRegistry
@ -129,6 +133,9 @@ classes = [
OT_Add_asset_filebrowser, OT_Add_asset_filebrowser,
Blenvy_assets, Blenvy_assets,
Blenvy_levels,
OT_select_level,
BlueprintsRegistry, BlueprintsRegistry,
OT_select_blueprint, OT_select_blueprint,
GLTF_PT_auto_export_blueprints_list, GLTF_PT_auto_export_blueprints_list,

View File

@ -4,21 +4,27 @@ from .serialize_scene import serialize_scene
from blenvy.settings import load_settings, upsert_settings from blenvy.settings import load_settings, upsert_settings
def bubble_up_changes(object, changes_per_scene): def bubble_up_changes(object, changes_per_scene):
if object.parent: if object is not None and object.parent:
changes_per_scene[object.parent.name] = bpy.data.objects[object.parent.name] changes_per_scene[object.parent.name] = bpy.data.objects[object.parent.name]
bubble_up_changes(object.parent, changes_per_scene) bubble_up_changes(object.parent, changes_per_scene)
import uuid
def serialize_current(settings): def serialize_current(settings):
# sigh... you need to save & reset the frame otherwise it saves the values AT THE CURRENT FRAME WHICH CAN DIFFER ACROSS SCENES # sigh... you need to save & reset the frame otherwise it saves the values AT THE CURRENT FRAME WHICH CAN DIFFER ACROSS SCENES
current_frames = [scene.frame_current for scene in bpy.data.scenes] current_frames = [scene.frame_current for scene in bpy.data.scenes]
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
scene.frame_set(0) scene.frame_set(0)
if scene.id_test == '':
print("GENERATE ID")
scene.id_test = str(uuid.uuid4())
print("SCENE ID", scene.id_test)
current_scene = bpy.context.window.scene current_scene = bpy.context.window.scene
bpy.context.window.scene = bpy.data.scenes[0] bpy.context.window.scene = bpy.data.scenes[0]
#serialize scene at frame 0 #serialize scene at frame 0
"""with bpy.context.temp_override(scene=bpy.data.scenes[1]): """with bpy.context.temp_override(scene=bpy.data.scenes[1]):
bpy.context.scene.frame_set(0)""" bpy.context.scene.frame_set(0)"""
current = serialize_scene(settings) current = serialize_scene(settings)
bpy.context.window.scene = current_scene bpy.context.window.scene = current_scene
@ -32,8 +38,13 @@ def get_changes_per_scene(settings):
previous = load_settings(".blenvy.project_serialized_previous") previous = load_settings(".blenvy.project_serialized_previous")
current = serialize_current(settings) current = serialize_current(settings)
# determine changes # determine changes
changes_per_scene = project_diff(previous, current) changes_per_scene = {}
try:
changes_per_scene = project_diff(previous, current, settings)
except Exception as error:
print("failed to compare current serialized scenes to previous ones", error)
# save the current project as previous # save the current project as previous
upsert_settings(".blenvy.project_serialized_previous", current, overwrite=True) upsert_settings(".blenvy.project_serialized_previous", current, overwrite=True)
@ -42,27 +53,34 @@ def get_changes_per_scene(settings):
return changes_per_scene return changes_per_scene
def project_diff(previous, current): def project_diff(previous, current, settings):
"""print("previous", previous) """print("previous", previous)
print("current", current)""" print("current", current)"""
if previous is None or current is None: if previous is None or current is None:
return {} return {}
print("HERE") print("Settings", settings,"current", current, "previous", previous)
changes_per_scene = {} changes_per_scene = {}
# TODO : how do we deal with changed scene names ??? # TODO : how do we deal with changed scene names ???
# possible ? on each save, inject an id into each scene, that cannot be copied over
print('TEST SCENE', bpy.data.scenes.get("ULTRA LEVEL2"), None)
for scene in current: for scene in current:
print("SCENE", scene) print("SCENE", scene)
previous_object_names = list(previous[scene].keys())
current_object_names =list(current[scene].keys()) current_object_names =list(current[scene].keys())
if scene in previous: # we can only compare scenes that are in both previous and current data
previous_object_names = list(previous[scene].keys())
added = list(set(current_object_names) - set(previous_object_names)) added = list(set(current_object_names) - set(previous_object_names))
removed = list(set(previous_object_names) - set(current_object_names)) removed = list(set(previous_object_names) - set(current_object_names))
for obj in added: for obj in added:
if not scene in changes_per_scene: if not scene in changes_per_scene:
changes_per_scene[scene] = {} changes_per_scene[scene] = {}
changes_per_scene[scene][obj] = bpy.data.objects[obj] changes_per_scene[scene][obj] = bpy.data.objects[obj] if obj in bpy.data.objects else None
# TODO: how do we deal with this, as we obviously do not have data for removed objects ? # TODO: how do we deal with this, as we obviously do not have data for removed objects ?
for obj in removed: for obj in removed:
@ -80,8 +98,11 @@ def project_diff(previous, current):
if not scene in changes_per_scene: if not scene in changes_per_scene:
changes_per_scene[scene] = {} changes_per_scene[scene] = {}
changes_per_scene[scene][object_name] = bpy.data.objects[object_name] target_object = bpy.data.objects[object_name] if object_name in bpy.data.objects else None
bubble_up_changes(bpy.data.objects[object_name], changes_per_scene[scene]) changes_per_scene[scene][object_name] = target_object
bubble_up_changes(target_object, changes_per_scene[scene])
# now bubble up for instances & parents # now bubble up for instances & parents
else:
print(f"scene {scene} not present in previous data")
return changes_per_scene return changes_per_scene

View File

@ -50,22 +50,75 @@ def _lookup_collection(data):
def _lookup_materialLineArt(data): def _lookup_materialLineArt(data):
return generic_fields_hasher_evolved(data, fields_to_ignore=fields_to_ignore_generic) return generic_fields_hasher_evolved(data, fields_to_ignore=fields_to_ignore_generic)
# used for various node trees: shaders, modifiers etc
def node_tree(node_tree):
print("SCANNING NODE TREE", node_tree)
# storage for hashing
links_hashes = []
nodes_hashes = []
root_inputs = dict(node_tree) # probably useless for materials, contains settings for certain modifiers
for node in node_tree.nodes:
#print("node", node, node.type, node.name, node.label)
input_hashes = []
for input in node.inputs:
#print(" input", input, "label", input.label, "name", input.name, dir(input))
default_value = getattr(input, 'default_value', None)
input_hash = f"{convert_field(default_value)}"
input_hashes.append(input_hash)
output_hashes = []
# IF the node itself is a group input, its outputs are the inputs of the geometry node (yes, not easy)
node_in_use = True
for (index, output) in enumerate(node.outputs):
# print(" output", output, "label", output.label, "name", output.name, "generated name", f"Socket_{index+1}")
default_value = getattr(output, 'default_value', None)
output_hash = f"{convert_field(default_value)}"
output_hashes.append(output_hash)
node_in_use = node_in_use and default_value is not None
#print("NODE IN USE", node_in_use)
node_fields_to_ignore = fields_to_ignore_generic + ['internal_links', 'inputs', 'outputs']
node_hash = f"{generic_fields_hasher_evolved(node, node_fields_to_ignore)}_{str(input_hashes)}_{str(output_hashes)}"
#print("node hash", node_hash)
#print("node hash", str(input_hashes))
nodes_hashes.append(node_hash)
for link in node_tree.links:
"""print("LINK", link, dir(link))
print("FROM", link.from_node, link.from_socket)
print("TO", link.to_node, link.to_socket)"""
from_socket_default = link.from_socket.default_value if hasattr(link.from_socket, "default_value") else None
to_socket_default = link.to_socket.default_value if hasattr(link.to_socket, "default_value") else None
link_hash = f"{link.from_node.name}_{link.from_socket.name}_{from_socket_default}+{link.to_node.name}_{link.to_socket.name}_{to_socket_default}"
links_hashes.append(link_hash)
#print("node hashes",nodes_hashes, "links_hashes", links_hashes)
print("root_inputs", root_inputs)
return f"{str(root_inputs)}_{str(nodes_hashes)}_{str(links_hashes)}"
type_lookups = { type_lookups = {
Color: _lookup_color,#lambda input: print("dsf")', Color: _lookup_color,#lambda input: print("dsf")',
bpy.types.FloatVectorAttribute: _lookup_array2, bpy.types.FloatVectorAttribute: _lookup_array2,
bpy.types.bpy_prop_array: _lookup_array, bpy.types.bpy_prop_array: _lookup_array,
bpy.types.PropertyGroup: _lookup_prop_group, bpy.types.PropertyGroup: _lookup_prop_group,
bpy.types.bpy_prop_collection: _lookup_collection, bpy.types.bpy_prop_collection: _lookup_collection,
bpy.types.MaterialLineArt: _lookup_materialLineArt bpy.types.MaterialLineArt: _lookup_materialLineArt,
bpy.types.NodeTree: node_tree,
} }
def convert_field(raw_value, field_name="", scan_node_tree=True): def convert_field(raw_value, field_name="", scan_node_tree=True):
# nodes are a special case: # TODO: find out their types & move these to type lookups """# nodes are a special case: # TODO: find out their types & move these to type lookups
if field_name in ["node_tree", "node_group"] and scan_node_tree: if field_name in ["node_tree", "node_group"] and scan_node_tree:
print("scan node tree") print("scan node tree", inspect.getmro(type(raw_value)))
return node_tree(raw_value) return node_tree(raw_value)
"""
conversion_lookup = None # type_lookups.get(type(raw_value), None) conversion_lookup = None # type_lookups.get(type(raw_value), None)
all_types = inspect.getmro(type(raw_value)) all_types = inspect.getmro(type(raw_value))
for s_type in all_types: for s_type in all_types:
@ -83,6 +136,7 @@ def convert_field(raw_value, field_name="", scan_node_tree=True):
return field_value return field_value
# just a helper , for shorthand
def obj_to_dict(object): def obj_to_dict(object):
try: try:
return dict(object) return dict(object)
@ -194,59 +248,6 @@ def armature_hash(obj):
print("bone", bone, bone_hash(bone))""" print("bone", bone, bone_hash(bone))"""
return str(fields) return str(fields)
# used for various node trees: shaders, modifiers etc
def node_tree(node_tree):
print("SCANNING NODE TREE", node_tree)
# storage for hashing
links_hashes = []
nodes_hashes = []
root_inputs = dict(node_tree) # probably useless for materials, contains settings for certain modifiers
for node in node_tree.nodes:
#print("node", node, node.type, node.name, node.label)
input_hashes = []
for input in node.inputs:
#print(" input", input, "label", input.label, "name", input.name, dir(input))
default_value = getattr(input, 'default_value', None)
input_hash = f"{convert_field(default_value)}"
input_hashes.append(input_hash)
output_hashes = []
# IF the node itself is a group input, its outputs are the inputs of the geometry node (yes, not easy)
node_in_use = True
for (index, output) in enumerate(node.outputs):
# print(" output", output, "label", output.label, "name", output.name, "generated name", f"Socket_{index+1}")
default_value = getattr(output, 'default_value', None)
output_hash = f"{convert_field(default_value)}"
output_hashes.append(output_hash)
node_in_use = node_in_use and default_value is not None
#print("NODE IN USE", node_in_use)
node_fields_to_ignore = fields_to_ignore_generic + ['internal_links', 'inputs', 'outputs']
node_hash = f"{generic_fields_hasher_evolved(node, node_fields_to_ignore)}_{str(input_hashes)}_{str(output_hashes)}"
#print("node hash", node_hash)
#print("node hash", str(input_hashes))
nodes_hashes.append(node_hash)
for link in node_tree.links:
"""print("LINK", link, dir(link))
print("FROM", link.from_node, link.from_socket)
print("TO", link.to_node, link.to_socket)"""
from_socket_default = link.from_socket.default_value if hasattr(link.from_socket, "default_value") else None
to_socket_default = link.to_socket.default_value if hasattr(link.to_socket, "default_value") else None
link_hash = f"{link.from_node.name}_{link.from_socket.name}_{from_socket_default}+{link.to_node.name}_{link.to_socket.name}_{to_socket_default}"
links_hashes.append(link_hash)
#print("node hashes",nodes_hashes, "links_hashes", links_hashes)
print("root_inputs", root_inputs)
return f"{str(root_inputs)}_{str(nodes_hashes)}_{str(links_hashes)}"
def material_hash(material, settings): def material_hash(material, settings):
scan_node_tree = settings.auto_export.materials_in_depth_scan scan_node_tree = settings.auto_export.materials_in_depth_scan
hashed_material_except_node_tree = generic_fields_hasher_evolved(material, fields_to_ignore_generic, scan_node_tree=scan_node_tree) hashed_material_except_node_tree = generic_fields_hasher_evolved(material, fields_to_ignore_generic, scan_node_tree=scan_node_tree)
@ -290,10 +291,29 @@ def serialize_scene(settings):
cache = {"materials":{}} cache = {"materials":{}}
print("serializing scene") print("serializing scene")
data = {} data = {}
# render settings are injected into each scene
# TODO: only go through scenes actually in our list
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
# ignore temporary scenes
if scene.name.startswith(TEMPSCENE_PREFIX): if scene.name.startswith(TEMPSCENE_PREFIX):
continue continue
data[scene.name] = {} data[scene.name] = {}
custom_properties = custom_properties_hash(scene) if len(scene.keys()) > 0 else None
eevee_settings = generic_fields_hasher_evolved(scene.eevee, fields_to_ignore=fields_to_ignore_generic) # TODO: ignore most of the fields
scene_field_hashes = {
"custom_properties": custom_properties,
"eevee": eevee_settings
}
print("SCENE WORLD", scene.world, dir(scene.eevee))
#generic_fields_hasher_evolved(scene.eevee, fields_to_ignore=fields_to_ignore_generic)
data[scene.name]["____scene_settings"] = str(hash(str(scene_field_hashes)))
for object in scene.objects: for object in scene.objects:
object = bpy.data.objects[object.name] object = bpy.data.objects[object.name]

View File

@ -43,6 +43,8 @@ class AutoExportTracker(PropertyGroup):
def register(cls): def register(cls):
bpy.types.WindowManager.auto_export_tracker = PointerProperty(type=AutoExportTracker) bpy.types.WindowManager.auto_export_tracker = PointerProperty(type=AutoExportTracker)
bpy.types.Scene.id_test = StringProperty(default="")
# setup handlers for updates & saving # setup handlers for updates & saving
#bpy.app.handlers.save_post.append(cls.save_handler) #bpy.app.handlers.save_post.append(cls.save_handler)
#bpy.app.handlers.depsgraph_update_post.append(cls.deps_update_handler) #bpy.app.handlers.depsgraph_update_post.append(cls.deps_update_handler)
@ -58,6 +60,8 @@ class AutoExportTracker(PropertyGroup):
except:pass""" except:pass"""
del bpy.types.WindowManager.auto_export_tracker del bpy.types.WindowManager.auto_export_tracker
del bpy.types.Scene.id_test
@classmethod @classmethod
def save_handler(cls, scene, depsgraph): def save_handler(cls, scene, depsgraph):
print("-------------") print("-------------")
@ -73,6 +77,7 @@ class AutoExportTracker(PropertyGroup):
@classmethod @classmethod
def deps_post_update_handler(cls, scene, depsgraph): def deps_post_update_handler(cls, scene, depsgraph):
# print("change detection enabled", cls.change_detection_enabled) # print("change detection enabled", cls.change_detection_enabled)
print("change detected", list(map(lambda x: x.name, list(bpy.data.scenes))))
"""ops = bpy.context.window_manager.operators """ops = bpy.context.window_manager.operators
print("last operators", ops) print("last operators", ops)

View File

@ -145,9 +145,9 @@ class OT_Add_asset_filebrowser(Operator, ImportHelper):
filter_glob: StringProperty(options={'HIDDEN'}, default='*.jpg;*.jpeg;*.png;*.bmp') # type: ignore filter_glob: StringProperty(options={'HIDDEN'}, default='*.jpg;*.jpeg;*.png;*.bmp') # type: ignore
def execute(self, context): def execute(self, context):
current_auto_settings = load_settings(".gltf_auto_export_settings") blenvy = context.window_manager.blenvy
project_root_path = current_auto_settings.get("project_root_path", "../") project_root_path = blenvy.project_root_path
assets_path = current_auto_settings.get("assets_path", "assets") assets_path = blenvy.assets_path
# FIXME: not sure # FIXME: not sure
print("project_root_path", project_root_path, "assets_path", assets_path) print("project_root_path", project_root_path, "assets_path", assets_path)
export_assets_path_absolute = absolute_path_from_blend_file(os.path.join(project_root_path, assets_path)) export_assets_path_absolute = absolute_path_from_blend_file(os.path.join(project_root_path, assets_path))

View File

@ -5,7 +5,7 @@ from .asset_helpers import get_user_assets
def draw_assets(layout, name, title, asset_registry, target_type, target_name, editable=True, user_assets= [], generated_assets = []): def draw_assets(layout, name, title, asset_registry, target_type, target_name, editable=True, user_assets= [], generated_assets = []):
header, panel = layout.box().panel(f"assets{name}", default_closed=False) header, panel = layout.panel(f"assets{name}", default_closed=False)
header.label(text=title) header.label(text=title)
if panel: if panel:
if editable: if editable:
@ -92,6 +92,8 @@ class Blenvy_assets(bpy.types.Panel):
user_assets = get_user_assets(scene) user_assets = get_user_assets(scene)
#print("user assets", user_assets, scene) #print("user assets", user_assets, scene)
row = panel.row() row = panel.row()
row.prop(scene, "always_export")
scene_assets_panel = draw_assets(layout=row, name=scene.name, title=f"{scene.name} Assets", asset_registry=asset_registry, user_assets=user_assets, target_type="SCENE", target_name=scene.name) scene_assets_panel = draw_assets(layout=row, name=scene.name, title=f"{scene.name} Assets", asset_registry=asset_registry, user_assets=user_assets, target_type="SCENE", target_name=scene.name)
"""if scene.name in blueprints_data.blueprint_instances_per_main_scene: """if scene.name in blueprints_data.blueprint_instances_per_main_scene:
for blueprint_name in blueprints_data.blueprint_instances_per_main_scene[scene.name].keys(): for blueprint_name in blueprints_data.blueprint_instances_per_main_scene[scene.name].keys():

View File

@ -30,6 +30,8 @@ class GLTF_PT_auto_export_blueprints_list(bpy.types.Panel):
row.label(icon="RIGHTARROW") row.label(icon="RIGHTARROW")
row.label(text=blueprint.name) row.label(text=blueprint.name)
row.prop(blueprint.collection, "always_export")
if blueprint.local: if blueprint.local:
select_blueprint = row.operator(operator="blueprint.select", text="", icon="RESTRICT_SELECT_OFF") select_blueprint = row.operator(operator="blueprint.select", text="", icon="RESTRICT_SELECT_OFF")

View File

@ -48,6 +48,7 @@ class BlenvyManager(PropertyGroup):
items=( items=(
('COMPONENTS', "Components", ""), ('COMPONENTS', "Components", ""),
('BLUEPRINTS', "Blueprints", ""), ('BLUEPRINTS', "Blueprints", ""),
('LEVELS', "Levels", ""),
('ASSETS', "Assets", ""), ('ASSETS', "Assets", ""),
('SETTINGS', "Settings", ""), ('SETTINGS', "Settings", ""),
('TOOLS', "Tools", ""), ('TOOLS', "Tools", ""),
@ -135,10 +136,19 @@ class BlenvyManager(PropertyGroup):
def register(cls): def register(cls):
bpy.types.WindowManager.blenvy = PointerProperty(type=BlenvyManager) bpy.types.WindowManager.blenvy = PointerProperty(type=BlenvyManager)
# unsure
# you can add components to both objects & collections
#bpy.types.Object.components_meta = PointerProperty(type=ComponentsMeta)
bpy.types.Collection.always_export = BoolProperty(default=False, description="always export this blueprint, regardless of changed status") # FIXME: not sure about this one
bpy.types.Scene.always_export = BoolProperty(default=False, description="always export this blueprint, regardless of changed status") # FIXME: not sure about this one
@classmethod @classmethod
def unregister(cls): def unregister(cls):
del bpy.types.WindowManager.blenvy del bpy.types.WindowManager.blenvy
del bpy.types.Collection.always_export
del bpy.types.Scene.always_export
def load_settings(self): def load_settings(self):
print("LOAD SETTINGS") print("LOAD SETTINGS")
settings = load_settings(self.settings_save_path) settings = load_settings(self.settings_save_path)

View File

@ -10,11 +10,11 @@ class OT_switch_bevy_tooling(Operator):
bl_label = "Switch bevy tooling" bl_label = "Switch bevy tooling"
#bl_options = {} #bl_options = {}
tool: EnumProperty( tool: EnumProperty(
items=( items=(
('COMPONENTS', "Components", "Switch to components"), ('COMPONENTS', "Components", "Switch to components"),
('BLUEPRINTS', "Blueprints", ""), ('BLUEPRINTS', "Blueprints", ""),
('LEVELS', "Levels", ""),
('ASSETS', "Assets", ""), ('ASSETS', "Assets", ""),
('SETTINGS', "Settings", ""), ('SETTINGS', "Settings", ""),
('TOOLS', "Tools", ""), ('TOOLS', "Tools", ""),

View File

@ -57,6 +57,10 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
tool_switch_components = target.operator(operator="bevy.tooling_switch", text="", icon="PACKAGE") tool_switch_components = target.operator(operator="bevy.tooling_switch", text="", icon="PACKAGE")
tool_switch_components.tool = "BLUEPRINTS" tool_switch_components.tool = "BLUEPRINTS"
target = row.box() if active_mode == 'LEVELS' else row
tool_switch_components = target.operator(operator="bevy.tooling_switch", text="", icon="PACKAGE")
tool_switch_components.tool = "LEVELS"
target = row.box() if active_mode == 'ASSETS' else row target = row.box() if active_mode == 'ASSETS' else row
tool_switch_components = target.operator(operator="bevy.tooling_switch", text="", icon="ASSET_MANAGER") tool_switch_components = target.operator(operator="bevy.tooling_switch", text="", icon="ASSET_MANAGER")
tool_switch_components.tool = "ASSETS" tool_switch_components.tool = "ASSETS"

View File

View File

@ -0,0 +1,26 @@
import os
import bpy
from bpy_types import (Operator)
from bpy.props import (StringProperty)
class OT_select_level(Operator):
"""Select level """
bl_idname = "level.select"
bl_label = "Select level"
bl_options = {"UNDO"}
level_name: StringProperty(
name="level name",
description="level to select",
) # type: ignore
def execute(self, context):
if self.level_name:
scene = bpy.data.scenes[self.level_name]
if scene:
# bpy.ops.object.select_all(action='DESELECT')
bpy.context.window.scene = scene
return {'FINISHED'}

77
tools/blenvy/levels/ui.py Normal file
View File

@ -0,0 +1,77 @@
from types import SimpleNamespace
import bpy
from ..assets.assets_scan import get_main_scene_assets_tree
from ..assets.asset_helpers import get_user_assets
from ..assets.ui import draw_assets
class Blenvy_levels(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_label = ""
bl_parent_id = "BLENVY_PT_SidePanel"
bl_options = {'DEFAULT_CLOSED','HIDE_HEADER'}
@classmethod
def poll(cls, context):
return context.window_manager.blenvy.mode == 'LEVELS'
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
blenvy = context.window_manager.blenvy
layout.operator(operator="bevyassets.test")
asset_registry = context.window_manager.assets_registry
blueprints_registry = context.window_manager.blueprints_registry
#blueprints_registry.refresh_blueprints()
blueprints_data = blueprints_registry.blueprints_data
for scene_selector in blenvy.main_scenes:
scene = bpy.data.scenes[scene_selector.name]
header, panel = layout.box().panel(f"assets{scene.name}", default_closed=False)
if header:
header.label(text=scene.name, icon="HIDE_OFF")
header.prop(scene, "always_export")
select_level = header.operator(operator="level.select", text="", icon="RESTRICT_SELECT_OFF")
select_level.level_name = scene.name
if panel:
user_assets = get_user_assets(scene)
row = panel.row()
#row.label(text="row")
"""col = row.column()
col.label(text=" ")
col = row.column()
col.label(text="col in row 2")
column = panel.column()
column.label(text="col")"""
split = panel.split(factor=0.005)
col = split.column()
col.label(text=" ")
col = split.column()
#col.label(text="col in row 2")
scene_assets_panel = draw_assets(layout=col, name=f"{scene.name}_assets", title=f"Assets", asset_registry=asset_registry, user_assets=user_assets, target_type="SCENE", target_name=scene.name)
settings = {"blueprints_path": "blueprints", "export_gltf_extension": ".glb"}
settings = SimpleNamespace(**settings)
"""if panel:
for scene_selector in blenvy.main_scenes:
scene = bpy.data.scenes[scene_selector.name]
#get_main_scene_assets_tree(scene, blueprints_data, settings)
user_assets = get_user_assets(scene)
#print("user assets", user_assets, scene)
row = panel.row()
header.prop(scene, "always_export")
sub_header, sub_panel = row.box().panel(f"assets{name}", default_closed=False)
scene_assets_panel = draw_assets(layout=sub_panel, name=scene.name, title=f"{scene.name} Assets", asset_registry=asset_registry, user_assets=user_assets, target_type="SCENE", target_name=scene.name)
"""