diff --git a/extension/__init__.py b/extension/__init__.py index 242fef1..4b658f6 100644 --- a/extension/__init__.py +++ b/extension/__init__.py @@ -1,11 +1,13 @@ import bpy # type: ignore import json from bpy.app.handlers import persistent +from .op_insert_component_modal import InsertComponent, SelectedObjectOptions from .op_apply_preset import ApplyPresetToBone, ApplyPresetToCollection, ApplyPresetToLight, ApplyPresetToMaterial, ApplyPresetToMesh, ApplyPresetToObject, ApplyPresetToScene from .cli_dump_component_data import dump_component_data # type: ignore -from .op_insert_component import InsertComponentOnBone, InsertComponentOnCollection, InsertComponentOnLight, InsertComponentOnMaterial, InsertComponentOnMesh, InsertComponentOnObject, InsertComponentOnScene +from .op_insert_component import InsertComponentOnBone, InsertComponentOnCollection, InsertComponentOnLight, InsertComponentOnMaterial, InsertComponentOnMesh, InsertComponentOnObject, InsertComponentOnWindowManager, InsertComponentOnScene +# from .op_insert_component_modal import InsertComponent from .op_registry_loading import FetchRemoteTypeRegistry, ReloadSkeinRegistryJson -from .op_remove_component import RemoveComponentOnBone, RemoveComponentOnCollection, RemoveComponentOnLight, RemoveComponentOnMaterial, RemoveComponentOnMesh, RemoveComponentOnObject, RemoveComponentOnScene +from .op_remove_component import RemoveComponentOnBone, RemoveComponentOnCollection, RemoveComponentOnLight, RemoveComponentOnMaterial, RemoveComponentOnMesh, RemoveComponentOnObject, RemoveComponentOnScene, RemoveComponentOnWindowManager from .op_debug_check_components import DebugCheckComponents from .property_groups import ComponentData from .skein_panel import SkeinPanelBone, SkeinPanelCollection, SkeinPanelLight, SkeinPanelObject, SkeinPanelMesh, SkeinPanelMaterial, SkeinPanelScene @@ -92,6 +94,10 @@ def menu_func(self, context): self.layout.operator(ReloadSkeinRegistryJson.bl_idname) def register(): + # TODO: move this + bpy.utils.register_class(SelectedObjectOptions) + + bpy.utils.register_class(SkeinAddonPreferences) # data types that are stored on the window because blender # doesn't seem to have any other good way of storing data @@ -135,6 +141,11 @@ def register(): override={"LIBRARY_OVERRIDABLE"}, ) + ## Used for temporary data, like operators + bpy.types.WindowManager.active_component_index = bpy.props.IntProperty( + min=0 + ) + # TODO: move this to common property group for all object, material, mesh, etc extras bpy.types.WindowManager.selected_component = bpy.props.StringProperty( name="component type path", @@ -150,6 +161,7 @@ def register(): bpy.utils.register_class(FetchRemoteTypeRegistry) bpy.utils.register_class(ReloadSkeinRegistryJson) bpy.utils.register_class(DebugCheckComponents) + # bpy.utils.register_class(InsertComponent) ## Insertion Operations bpy.utils.register_class(InsertComponentOnObject) bpy.utils.register_class(InsertComponentOnMesh) @@ -158,6 +170,8 @@ def register(): bpy.utils.register_class(InsertComponentOnLight) bpy.utils.register_class(InsertComponentOnCollection) bpy.utils.register_class(InsertComponentOnBone) + bpy.utils.register_class(InsertComponentOnWindowManager) + bpy.utils.register_class(InsertComponent) ## Remove Operations bpy.utils.register_class(RemoveComponentOnObject) bpy.utils.register_class(RemoveComponentOnMesh) @@ -166,6 +180,7 @@ def register(): bpy.utils.register_class(RemoveComponentOnLight) bpy.utils.register_class(RemoveComponentOnCollection) bpy.utils.register_class(RemoveComponentOnBone) + bpy.utils.register_class(RemoveComponentOnWindowManager) ## Preset Operations bpy.utils.register_class(ApplyPresetToObject) bpy.utils.register_class(ApplyPresetToMesh) @@ -207,6 +222,9 @@ def register(): exporter_extension_layout_draw['Example glTF Extension'] = draw_export # Make sure to use the same name in unregister() def unregister(): + # TODO: move this + bpy.utils.unregister_class(SelectedObjectOptions) + global_skein = bpy.context.window_manager.skein skein_property_groups = bpy.context.window_manager.skein_property_groups @@ -234,6 +252,7 @@ def unregister(): bpy.utils.unregister_class(PGSkeinWindowProps) bpy.utils.unregister_class(ComponentTypeData) bpy.utils.unregister_class(ComponentData) + # bpy.utils.unregister_class(InsertComponent) # operations bpy.utils.unregister_class(FetchRemoteTypeRegistry) bpy.utils.unregister_class(ReloadSkeinRegistryJson) @@ -244,12 +263,17 @@ def unregister(): bpy.utils.unregister_class(InsertComponentOnMaterial) bpy.utils.unregister_class(InsertComponentOnLight) bpy.utils.unregister_class(InsertComponentOnCollection) + bpy.utils.unregister_class(InsertComponentOnWindowManager) + bpy.utils.unregister_class(InsertComponent) ## Remove Operations bpy.utils.unregister_class(RemoveComponentOnObject) bpy.utils.unregister_class(RemoveComponentOnMesh) bpy.utils.unregister_class(RemoveComponentOnMaterial) + bpy.utils.unregister_class(RemoveComponentOnScene) bpy.utils.unregister_class(RemoveComponentOnLight) bpy.utils.unregister_class(RemoveComponentOnCollection) + bpy.utils.unregister_class(RemoveComponentOnBone) + bpy.utils.unregister_class(RemoveComponentOnWindowManager) ## Preset Operations bpy.utils.unregister_class(ApplyPresetToObject) bpy.utils.unregister_class(ApplyPresetToMesh) diff --git a/extension/object_to_form.py b/extension/object_to_form.py index 39d6c03..06342a2 100644 --- a/extension/object_to_form.py +++ b/extension/object_to_form.py @@ -7,7 +7,7 @@ def object_to_form(context, context_key, data): data is the component data """ if context_key not in context: - return + return # The current PropertyGroup we're working with obj = getattr(context, context_key) diff --git a/extension/op_insert_component.py b/extension/op_insert_component.py index c1d500d..74b1ea8 100644 --- a/extension/op_insert_component.py +++ b/extension/op_insert_component.py @@ -102,10 +102,28 @@ def execute(self, context): insert_component_data(context, context.bone) return {'FINISHED'} -def insert_component_data(context, obj): +class InsertComponentOnWindowManager(bpy.types.Operator): + """Insert a component on the window manager""" + bl_idname = "wm.insert_component" # unique identifier. first word is required by extensions review team to be from a specific set of words + bl_label = "Add Component" # Shows up in the UI + bl_options = {'REGISTER', 'UNDO'} # enable undo (which we might not need) + + # @classmethod + # def poll(cls, context): + # return context.bone is not None + + def execute(self, context): + insert_component_data(context, context.window_manager) + return {'FINISHED'} + +def insert_component_data(context, obj, type_path_override=None): """ Inserting data is super generic, the only difference is where we're inserting it. This is basically the same concept as Custom Properties which don't care what object they're on. + + Inserts using context.window_manager.selected_component if a type_path_override isn't set + + expects a full, raw type_path in type_path_override """ debug = False presets = False @@ -118,7 +136,7 @@ def insert_component_data(context, obj): print("\ninsert_component_data:") global_skein = context.window_manager.skein - selected_component = context.window_manager.selected_component + selected_component = type_path_override or context.window_manager.selected_component if global_skein.registry: registry = json.loads(global_skein.registry) diff --git a/extension/op_insert_component_modal.py b/extension/op_insert_component_modal.py new file mode 100644 index 0000000..92c05ae --- /dev/null +++ b/extension/op_insert_component_modal.py @@ -0,0 +1,123 @@ +import json +import bpy +import inspect + +from .form_to_object import get_data_from_active_editor + +from .op_insert_component import insert_component_data + +from .skein_panel import draw_generic_panel, render_two + +from .object_to_form import object_to_form +from .property_groups import hash_over_64 + +def get_targets(self, context): + match self.ty: + case "MESH": + return [ + ("active_material","active_material",""), + ] + case "LIGHT": + return [("idk","idk","")] + case "CAMERA": + return [("whatever","whatever","")] + + # If an exact match is not confirmed, this last case will be used if provided + case _: + return [("no options","no options","")] + +class SelectedObjectOptions(bpy.types.PropertyGroup): + """Options that correspond to selected objects + """ + ty: bpy.props.StringProperty() + targets: bpy.props.EnumProperty( + name="target", + items=get_targets, + default=0 + ) + +class InsertComponent(bpy.types.Operator): + """Insert Components on Selected Objects + + """ + bl_idname = 'wm.insert_component_modal' + bl_label = 'Insert Components' + bl_description ='Select objects and insert components' + bl_options = {'REGISTER', 'UNDO'} + + selected: bpy.props.CollectionProperty( + type=SelectedObjectOptions, + ) + # active_component_index = bpy.props.IntProperty( + # min=0, + # override={"LIBRARY_OVERRIDABLE"}, + # ) + # ty = bpy.props.EnumProperty( + # name="type", + # items=[ + # ("object","object","object is available"), + # ("mesh","mesh","mesh is available"), + # ("material","material","material is available") + # ], + # default="mesh" + # ) + # skein_two = bpy.props.PointerProperty() + # obj_file = bpy.props.StringProperty() + + def execute(self, context): + skein_property_groups = context.window_manager.skein_property_groups + + # Iterate over all targets with a skein_two field, setting up + # the component data for each. + # "with a skein_two field" is controlled by the bpy.types setups + # and the limits inflicted by the operator's enum lists + for i, target_object in enumerate(bpy.context.selected_objects): + user_selected_target_object = getattr(target_object, self.selected[i].targets) + for component in context.window_manager.skein_two: + type_path = component["selected_type_path"] + ## create the new component, touching all PointerProperty fields + ## with a "forced" type_path. + insert_component_data(context, user_selected_target_object, type_path) + + target_skein_two = user_selected_target_object.skein_two + # -1 is "last element in list" + component_container = target_skein_two[-1] + ## Get data from user-created component + if inspect.isclass(skein_property_groups[type_path]): + data = get_data_from_active_editor(component, hash_over_64(type_path)) + object_to_form( + component_container, + hash_over_64(type_path), + data + ) + else: + data = getattr(component, hash_over_64(type_path)) + setattr(component_container, hash_over_64(type_path), data) + + self.selected.clear() + return {'FINISHED'} + + def invoke(self, context, event): + # self.data = {} + # return self.execute(context) + wm = context.window_manager + ## clear any old skein_two data + wm.skein_two.clear() + for id in context.selected_ids: + print("adding", id.type) + select = self.selected.add() + select.ty = id.type + return wm.invoke_props_dialog(self, confirm_text="Insert Components") + + def draw(self, context): + split = self.layout.row() + col = split.column() + for selected in self.selected: + layout = col.column() + layout.label(text=selected.ty) + layout.prop(selected, "targets") + # draw_generic_panel(context, obj, self.layout, "object", "OBJECT_PT_skein_preset_panel") + draw_generic_panel(context, context.window_manager, split.column(), "wm", "OBJECT_PT_skein_preset_panel") + + + diff --git a/extension/op_registry_loading.py b/extension/op_registry_loading.py index 7c2c89c..300e2c2 100644 --- a/extension/op_registry_loading.py +++ b/extension/op_registry_loading.py @@ -4,6 +4,8 @@ import json import requests # type: ignore import os + +# from .op_insert_component_modal import build_insert_component_operator from .property_groups import hash_over_64, make_property # --------------------------------- # # Fetch and store the bevy type # @@ -31,16 +33,12 @@ def execute(self, context): brp_response = None try: - print("\nexecute: TODO: a") rpc_response = brp_simple_request("rpc.discover") - print("\nexecute: TODO: b") - print(rpc_response) if rpc_response is not None and "error" in rpc_response: if debug: print("bevy request errored out", rpc_response["error"]) self.report({"ERROR"}, "request for Bevy registry data returned an error, is the Bevy Remote Protocol Plugin added and is the Bevy app running? :: " + brp_response["error"]["message"]) return {'CANCELLED'} - print("\nexecute: TODO: c") bevy_version = rpc_response["result"]["info"]["version"] print(bevy_version) if bevy_version.startswith("0.16"): @@ -253,6 +251,13 @@ def process_registry(context, registry): # so adding to it is fine skein_property_groups["skein_internal_container"] = component_container bpy.utils.register_class(component_container) + # try: + # bpy.utils.unregister_class(skein_property_groups["skein_internal_insert_operator"]) + # except: + # # it doesn't matter why this failed, because sometimes it won't exist anyway + # pass + + # skein_property_groups["skein_internal_insert_operator"] = build_insert_component_operator(component_container) # new component list data. Must be set to read component data from .blend file bpy.types.Object.skein_two = bpy.props.CollectionProperty( @@ -287,3 +292,14 @@ def process_registry(context, registry): type=component_container, override={"LIBRARY_OVERRIDABLE"}, ) + + ## This is where we store temporary skein_two data. + ## for example, passing skein_two access back and forth in + ## the generic "insert_component" operator + ## + ## WindowManager gets cleared when reloading .blend/closing app + ## /etc so is fine for temporary data + bpy.types.WindowManager.skein_two = bpy.props.CollectionProperty( + type=component_container, + override={"LIBRARY_OVERRIDABLE"}, + ) diff --git a/extension/op_remove_component.py b/extension/op_remove_component.py index 6ace84e..b4daa06 100644 --- a/extension/op_remove_component.py +++ b/extension/op_remove_component.py @@ -97,6 +97,20 @@ def poll(cls, context): def execute(self, context): remove_component_data(context, context.bone) return {'FINISHED'} + +class RemoveComponentOnWindowManager(bpy.types.Operator): + """Remove a component""" + bl_idname = "wm.remove_component" # unique identifier. first word is required by extensions review team to be from a specific set of words + bl_label = "Remove Component" # Shows up in the UI + bl_options = {'REGISTER', 'UNDO'} # enable undo (which we might not need) + + # @classmethod + # def poll(cls, context): + # return context.bone is not None + + def execute(self, context): + remove_component_data(context, context.window_manager) + return {'FINISHED'} ### def remove_component_data(context, obj):