Compare commits

..

No commits in common. "f6b61f83ae0842b21656d1d04e1b8464281799dc" and "21a36f9878c0e609bfe018633b288eebe53833b9" have entirely different histories.

13 changed files with 67 additions and 64 deletions

View File

@ -48,17 +48,12 @@ Blueprints:
- [ ] scan & inject on load - [ ] scan & inject on load
- [ ] scan & inject on save - [ ] scan & inject on save
- [ ] decide where & when to do & store blueprints data
General things to solve: General things to solve:
- [x] save settings - [x] save settings
- [x] load settings - [x] load settings
- [ ] add_blueprints_data - [ ] add_blueprints_data
- [ ] rename all path stuff using the old naming convention : "export_blueprints_path_full"
- [ ] generate the full paths directly when setting them in the UI
- [ ] problem : how to deal with defaults: do it on start/load ?
General issues: General issues:
- there is no safeguard for naming collisions for naming across blender files - there is no safeguard for naming collisions for naming across blender files
- this can cause an issue for assets list "parent" - this can cause an issue for assets list "parent"

View File

@ -8,7 +8,7 @@ from .blueprint import Blueprint
# - with the "auto_export" flag # - with the "auto_export" flag
# https://blender.stackexchange.com/questions/167878/how-to-get-all-collections-of-the-current-scene # https://blender.stackexchange.com/questions/167878/how-to-get-all-collections-of-the-current-scene
def blueprints_scan(main_scenes, library_scenes, addon_prefs): def blueprints_scan(main_scenes, library_scenes, addon_prefs):
export_marked_assets = getattr(addon_prefs.auto_export,"export_marked_assets") export_marked_assets = getattr(addon_prefs,"export_marked_assets")
blueprints = {} blueprints = {}
blueprints_from_objects = {} blueprints_from_objects = {}

View File

@ -7,8 +7,6 @@ import blenvy.gltf_auto_export.settings as auto_export_settings
def update_scene_lists(self, context): def update_scene_lists(self, context):
blenvy = self# context.window_manager.blenvy blenvy = self# context.window_manager.blenvy
blenvy.main_scene_names = [scene.name for scene in blenvy.main_scenes] # FIXME: unsure
blenvy.library_scene_names = [scene.name for scene in blenvy.library_scenes] # FIXME: unsure
upsert_settings(blenvy.settings_save_path, {"common_main_scene_names": [scene.name for scene in blenvy.main_scenes]}) upsert_settings(blenvy.settings_save_path, {"common_main_scene_names": [scene.name for scene in blenvy.main_scenes]})
upsert_settings(blenvy.settings_save_path, {"common_library_scene_names": [scene.name for scene in blenvy.library_scenes]}) upsert_settings(blenvy.settings_save_path, {"common_library_scene_names": [scene.name for scene in blenvy.library_scenes]})
@ -76,15 +74,13 @@ class BlenvyManager(PropertyGroup):
main_scenes: CollectionProperty(name="main scenes", type=SceneSelector) # type: ignore main_scenes: CollectionProperty(name="main scenes", type=SceneSelector) # type: ignore
main_scenes_index: IntProperty(name = "Index for main scenes list", default = 0, update=update_scene_lists) # type: ignore main_scenes_index: IntProperty(name = "Index for main scenes list", default = 0, update=update_scene_lists) # type: ignore
main_scene_names = [] # FIXME: unsure
library_scenes: CollectionProperty(name="library scenes", type=SceneSelector) # type: ignore library_scenes: CollectionProperty(name="library scenes", type=SceneSelector) # type: ignore
library_scenes_index: IntProperty(name = "Index for library scenes list", default = 0, update=update_scene_lists) # type: ignore library_scenes_index: IntProperty(name = "Index for library scenes list", default = 0, update=update_scene_lists) # type: ignore
library_scene_names = [] # FIXME: unsure
# sub ones # sub ones
auto_export: PointerProperty(type=auto_export_settings.AutoExportSettings) # type: ignore auto_export: PointerProperty(type=auto_export_settings.AutoExportSettings) # type: ignore
#components: PointerProperty(type=bevy_component_settings.ComponentSettings) # type: ignore #components: PointerProperty(type=auto_export_settings.AutoExportSettings) # type: ignore
def is_scene_ok(self, scene): def is_scene_ok(self, scene):
try: try:

View File

@ -28,16 +28,16 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
# Get the folder # Get the folder
blend_file_path = os.path.dirname(file_path) blend_file_path = os.path.dirname(file_path)
print("settings", dict(addon_prefs))
# get the preferences for our addon # get the preferences for our addon
project_root_path = getattr(addon_prefs, "project_root_path") project_root_path = getattr(addon_prefs, "project_root_path")
assets_path = getattr(addon_prefs,"assets_path") assets_path = getattr(addon_prefs,"assets_path")
#should we use change detection or not #should we use change detection or not
change_detection = getattr(addon_prefs.auto_export, "change_detection") export_change_detection = getattr(addon_prefs, "export_change_detection")
export_scene_settings = getattr(addon_prefs.auto_export,"export_scene_settings") export_scene_settings = getattr(addon_prefs,"export_scene_settings")
do_export_blueprints = getattr(addon_prefs.auto_export,"export_blueprints")
export_materials_library = getattr(addon_prefs.auto_export,"export_materials_library") do_export_blueprints = getattr(addon_prefs,"export_blueprints")
export_materials_library = getattr(addon_prefs,"export_materials_library")
print("export_materials_library", export_materials_library) print("export_materials_library", export_materials_library)
# standard gltf export settings are stored differently # standard gltf export settings are stored differently
@ -45,6 +45,21 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
gltf_extension = standard_gltf_exporter_settings.get("export_format", 'GLB') gltf_extension = standard_gltf_exporter_settings.get("export_format", 'GLB')
gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf' gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf'
# here we do a bit of workaround by creating an override # TODO: do this at the "UI" level
print("collection_instances_combine_mode", addon_prefs.collection_instances_combine_mode)
"""if hasattr(addon_prefs, "__annotations__") :
tmp = {}
for k in AutoExportGltfAddonPreferences.__annotations__:
item = AutoExportGltfAddonPreferences.__annotations__[k]
#print("tutu",k, item.keywords.get('default', None) )
default = item.keywords.get('default', None)
tmp[k] = default
for (k, v) in addon_prefs.properties.items():
tmp[k] = v
addon_prefs = SimpleNamespace(**tmp) #copy.deepcopy(addon_prefs)
addon_prefs.__annotations__ = tmp"""
# generate the actual complete output paths # generate the actual complete output paths
addon_prefs.export_assets_path_full = os.path.join(blend_file_path, project_root_path, assets_path) addon_prefs.export_assets_path_full = os.path.join(blend_file_path, project_root_path, assets_path)
addon_prefs.export_blueprints_path_full = os.path.join(addon_prefs.export_assets_path_full, getattr(addon_prefs,"blueprints_path")) addon_prefs.export_blueprints_path_full = os.path.join(addon_prefs.export_assets_path_full, getattr(addon_prefs,"blueprints_path"))
@ -65,7 +80,7 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, blueprints_path=blueprints_path, gltf_extension=gltf_extension) inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, blueprints_path=blueprints_path, gltf_extension=gltf_extension)
for blueprint in blueprints_data.blueprints: for blueprint in blueprints_data.blueprints:
bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint) bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint)
#bpy.context.window_manager.blueprints_registry.add_blueprints_data() bpy.context.window_manager.blueprints_registry.add_blueprints_data()
if export_scene_settings: if export_scene_settings:
# inject/ update scene components # inject/ update scene components
@ -77,7 +92,7 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
# export # export
if do_export_blueprints: if do_export_blueprints:
print("EXPORTING", blueprints_data) print("EXPORTING")
# get blueprints/collections infos # get blueprints/collections infos
(blueprints_to_export) = get_blueprints_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs) (blueprints_to_export) = get_blueprints_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs)
@ -122,7 +137,7 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
export_main_scene(bpy.data.scenes[scene_name], blend_file_path, addon_prefs, blueprints_data) export_main_scene(bpy.data.scenes[scene_name], blend_file_path, addon_prefs, blueprints_data)
# now deal with blueprints/collections # now deal with blueprints/collections
do_export_library_scene = not change_detection or changed_export_parameters or len(blueprints_to_export) > 0 do_export_library_scene = not export_change_detection or changed_export_parameters or len(blueprints_to_export) > 0
if do_export_library_scene: if do_export_library_scene:
print("export LIBRARY") print("export LIBRARY")
export_blueprints(blueprints_to_export, addon_prefs, blueprints_data) export_blueprints(blueprints_to_export, addon_prefs, blueprints_data)

View File

@ -8,13 +8,13 @@ from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collectio
def export_blueprints(blueprints, addon_prefs, blueprints_data): def export_blueprints(blueprints, addon_prefs, blueprints_data):
export_blueprints_path_full = getattr(addon_prefs, "export_blueprints_path_full") export_blueprints_path_full = getattr(addon_prefs,"export_blueprints_path_full")
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) gltf_export_preferences = generate_gltf_export_preferences(addon_prefs)
try: try:
# save current active collection # save current active collection
active_collection = bpy.context.view_layer.active_layer_collection active_collection = bpy.context.view_layer.active_layer_collection
export_materials_library = getattr(addon_prefs.auto_export, "export_materials_library") export_materials_library = getattr(addon_prefs,"export_materials_library")
for blueprint in blueprints: for blueprint in blueprints:
print("exporting collection", blueprint.name) print("exporting collection", blueprint.name)

View File

@ -14,8 +14,8 @@ def export_main_scene(scene, blend_file_path, addon_prefs, blueprints_data):
export_assets_path_full = getattr(addon_prefs,"export_assets_path_full") export_assets_path_full = getattr(addon_prefs,"export_assets_path_full")
export_levels_path_full = getattr(addon_prefs,"export_levels_path_full") export_levels_path_full = getattr(addon_prefs,"export_levels_path_full")
export_blueprints = getattr(addon_prefs.auto_export,"export_blueprints") export_blueprints = getattr(addon_prefs,"export_blueprints")
export_separate_dynamic_and_static_objects = getattr(addon_prefs.auto_export, "export_separate_dynamic_and_static_objects") export_separate_dynamic_and_static_objects = getattr(addon_prefs, "export_separate_dynamic_and_static_objects")
export_settings = { **gltf_export_preferences, export_settings = { **gltf_export_preferences,
'use_active_scene': True, 'use_active_scene': True,

View File

@ -5,20 +5,20 @@ from ...blueprints.blueprint_helpers import find_blueprints_not_on_disk
# TODO: this should also take the split/embed mode into account: if a nested collection changes AND embed is active, its container collection should also be exported # TODO: this should also take the split/embed mode into account: if a nested collection changes AND embed is active, its container collection should also be exported
def get_blueprints_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs): def get_blueprints_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs):
export_change_detection = getattr(addon_prefs, "export_change_detection")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension", ".glb") export_gltf_extension = getattr(addon_prefs, "export_gltf_extension", ".glb")
export_blueprints_path_full = getattr(addon_prefs,"export_blueprints_path_full", "") export_blueprints_path_full = getattr(addon_prefs,"export_blueprints_path_full", "")
change_detection = getattr(addon_prefs.auto_export, "change_detection") collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
collection_instances_combine_mode = getattr(addon_prefs.auto_export, "collection_instances_combine_mode")
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs) [main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
internal_blueprints = blueprints_data.internal_blueprints internal_blueprints = blueprints_data.internal_blueprints
blueprints_to_export = internal_blueprints # just for clarity blueprints_to_export = internal_blueprints # just for clarity
# print("change_detection", change_detection, "changed_export_parameters", changed_export_parameters, "changes_per_scene", changes_per_scene) # print("export_change_detection", export_change_detection, "changed_export_parameters", changed_export_parameters, "changes_per_scene", changes_per_scene)
# if the export parameters have changed, bail out early # if the export parameters have changed, bail out early
# we need to re_export everything if the export parameters have been changed # we need to re_export everything if the export parameters have been changed
if change_detection and not changed_export_parameters: if export_change_detection and not changed_export_parameters:
changed_blueprints = [] changed_blueprints = []
# first check if all collections have already been exported before (if this is the first time the exporter is run # first check if all collections have already been exported before (if this is the first time the exporter is run

View File

@ -37,16 +37,15 @@ def changed_object_in_scene(scene_name, changes_per_scene, blueprints_data, coll
# this also takes the split/embed mode into account: if a collection instance changes AND embed is active, its container level/world should also be exported # this also takes the split/embed mode into account: if a collection instance changes AND embed is active, its container level/world should also be exported
def get_levels_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs): def get_levels_to_export(changes_per_scene, changed_export_parameters, blueprints_data, addon_prefs):
export_change_detection = getattr(addon_prefs, "export_change_detection")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension") export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
export_levels_path_full = getattr(addon_prefs, "export_levels_path_full") export_levels_path_full = getattr(addon_prefs, "export_levels_path_full")
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
change_detection = getattr(addon_prefs.auto_export, "change_detection")
collection_instances_combine_mode = getattr(addon_prefs.auto_export, "collection_instances_combine_mode")
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs) [main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
# determine list of main scenes to export # determine list of main scenes to export
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc) # we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
main_scenes_to_export = [scene_name for scene_name in main_scene_names if not change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or changed_object_in_scene(scene_name, changes_per_scene, blueprints_data, collection_instances_combine_mode) or not check_if_blueprint_on_disk(scene_name, export_levels_path_full, export_gltf_extension) ] main_scenes_to_export = [scene_name for scene_name in main_scene_names if not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or changed_object_in_scene(scene_name, changes_per_scene, blueprints_data, collection_instances_combine_mode) or not check_if_blueprint_on_disk(scene_name, export_levels_path_full, export_gltf_extension) ]
return (main_scenes_to_export) return (main_scenes_to_export)

View File

@ -22,7 +22,7 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
'auto_export', 'auto_export',
'project_root_path', 'project_root_path',
'assets_path', 'assets_path',
'change_detection', 'export_change_detection',
'export_scene_settings', 'export_scene_settings',
'main_scene_names', 'main_scene_names',
@ -132,7 +132,6 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
This should ONLY be run when actually doing exports/aka calling auto_export function, because we only care about the difference in settings between EXPORTS This should ONLY be run when actually doing exports/aka calling auto_export function, because we only care about the difference in settings between EXPORTS
""" """
def did_export_settings_change(self): def did_export_settings_change(self):
return True
# compare both the auto export settings & the gltf settings # compare both the auto export settings & the gltf settings
previous_auto_settings = bpy.data.texts[".gltf_auto_export_settings_previous"] if ".gltf_auto_export_settings_previous" in bpy.data.texts else None previous_auto_settings = bpy.data.texts[".gltf_auto_export_settings_previous"] if ".gltf_auto_export_settings_previous" in bpy.data.texts else None
previous_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings_previous"] if ".gltf_auto_export_gltf_settings_previous" in bpy.data.texts else None previous_gltf_settings = bpy.data.texts[".gltf_auto_export_gltf_settings_previous"] if ".gltf_auto_export_gltf_settings_previous" in bpy.data.texts else None
@ -184,28 +183,24 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
return changed return changed
def did_objects_change(self): def did_objects_change(self):
# FIXME: add it back pass
return {}
def execute(self, context): def execute(self, context):
print("execute auto export", context.window_manager.blenvy.auto_export)
blenvy = context.window_manager.blenvy
auto_export_settings = blenvy.auto_export
bpy.context.window_manager.auto_export_tracker.disable_change_detection() bpy.context.window_manager.auto_export_tracker.disable_change_detection()
if self.direct_mode: if self.direct_mode:
self.load_settings(context) self.load_settings(context)
if self.will_save_settings: if self.will_save_settings:
self.save_settings(context) self.save_settings(context)
#print("self", self.auto_export) #print("self", self.auto_export)
if auto_export_settings.auto_export: # only do the actual exporting if auto export is actually enabled if self.auto_export: # only do the actual exporting if auto export is actually enabled
print("auto export")
#changes_per_scene = context.window_manager.auto_export_tracker.changed_objects_per_scene #changes_per_scene = context.window_manager.auto_export_tracker.changed_objects_per_scene
#& do the export #& do the export
if self.direct_mode: #Do not auto export when applying settings in the menu, do it on save only
# determine changed objects # determine changed objects
changes_per_scene = self.did_objects_change() changes_per_scene = self.did_objects_change()
# determine changed parameters # determine changed parameters
params_changed = self.did_export_settings_change() params_changed = self.did_export_settings_change()
auto_export(changes_per_scene, params_changed, blenvy) auto_export(changes_per_scene, params_changed, self)
# cleanup # cleanup
# reset the list of changes in the tracker # reset the list of changes in the tracker
bpy.context.window_manager.auto_export_tracker.clear_changes() bpy.context.window_manager.auto_export_tracker.clear_changes()
@ -216,9 +211,11 @@ class AutoExportGLTF(Operator, AutoExportGltfAddonPreferences):#, ExportHelper):
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
print("invoke") #print("invoke")
bpy.context.window_manager.auto_export_tracker.disable_change_detection() bpy.context.window_manager.auto_export_tracker.disable_change_detection()
self.load_settings(context) self.load_settings(context)
wm = context.window_manager
#wm.fileselect_add(self)
return context.window_manager.invoke_props_dialog(self, title="Auto export", width=640) return context.window_manager.invoke_props_dialog(self, title="Auto export", width=640)
def cancel(self, context): def cancel(self, context):

View File

@ -19,7 +19,7 @@ AutoExportGltfPreferenceNames = [
'export_scene_settings', 'export_scene_settings',
'show_change_detection_settings', 'show_change_detection_settings',
'change_detection', 'export_change_detection',
'show_scene_settings', 'show_scene_settings',
'main_scenes', 'main_scenes',
@ -104,7 +104,7 @@ class AutoExportGltfAddonPreferences(AddonPreferences):
default=True default=True
) # type: ignore ) # type: ignore
change_detection: BoolProperty( export_change_detection: BoolProperty(
name='Change detection', name='Change detection',
description='Use change detection to determine what/if should be exported', description='Use change detection to determine what/if should be exported',
default=True default=True

View File

@ -146,7 +146,7 @@ def duplicate_object(object, parent, combine_mode, destination_collection, bluep
# copies the contents of a collection into another one while replacing library instances with empties # copies the contents of a collection into another one while replacing library instances with empties
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, blueprints_data=None, addon_prefs={}): def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, blueprints_data=None, addon_prefs={}):
collection_instances_combine_mode = getattr(addon_prefs.auto_export, "collection_instances_combine_mode") collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
for object in source_collection.objects: for object in source_collection.objects:
if object.name.endswith("____bak"): # some objects could already have been handled, ignore them if object.name.endswith("____bak"): # some objects could already have been handled, ignore them
@ -207,13 +207,9 @@ def clear_hollow_scene(temp_scene, original_root_collection):
# convenience utility to get lists of scenes # convenience utility to get lists of scenes
def get_scenes(addon_prefs): def get_scenes(addon_prefs):
level_scene_names= getattr(addon_prefs, "main_scene_names", []) #list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes"))) level_scene_names= getattr(addon_prefs,"main_scene_names", []) #list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes")))
library_scene_names = getattr(addon_prefs, "library_scene_names", []) #list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes"))) library_scene_names = getattr(addon_prefs,"library_scene_names", []) #list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes")))
level_scene_names= list(map(lambda scene: scene.name, getattr(addon_prefs,"main_scenes")))
library_scene_names = list(map(lambda scene: scene.name, getattr(addon_prefs,"library_scenes")))
print("level_scene_names", level_scene_names)
level_scene_names = list(filter(lambda name: name in bpy.data.scenes, level_scene_names)) level_scene_names = list(filter(lambda name: name in bpy.data.scenes, level_scene_names))
library_scene_names = list(filter(lambda name: name in bpy.data.scenes, library_scene_names)) library_scene_names = list(filter(lambda name: name in bpy.data.scenes, library_scene_names))

View File

@ -3,6 +3,11 @@ from bpy_types import (PropertyGroup)
from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty) from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProperty, CollectionProperty, IntProperty)
class AutoExportSettings(PropertyGroup): class AutoExportSettings(PropertyGroup):
# use when operator is called directly, works a bit differently than inside the ui
direct_mode: BoolProperty(
default=False
) # type: ignore
auto_export: BoolProperty( auto_export: BoolProperty(
name='Auto export', name='Auto export',
description='Automatically export to gltf on save', description='Automatically export to gltf on save',
@ -10,7 +15,7 @@ class AutoExportSettings(PropertyGroup):
) # type: ignore ) # type: ignore
#### general #### general
change_detection: BoolProperty( export_change_detection: BoolProperty(
name='Change detection', name='Change detection',
description='Use change detection to determine what/if should be exported', description='Use change detection to determine what/if should be exported',
default=True default=True

View File

@ -29,7 +29,7 @@ def draw_settings_ui(layout, auto_export_settings):
if panel: if panel:
section = panel.box() section = panel.box()
section.enabled = controls_enabled section.enabled = controls_enabled
section.prop(auto_export_settings, "change_detection", text="Use change detection") section.prop(auto_export_settings, "export_change_detection", text="Use change detection")
header, panel = layout.panel("Blueprints", default_closed=False) header, panel = layout.panel("Blueprints", default_closed=False)
header.label(text="Blueprints") header.label(text="Blueprints")