diff --git a/README.md b/README.md index 152e290..18fcd69 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ The YouTube tutorials use three different .blend example files. This repository - [Step 1. Create NFT Data](#step-1---create-nft-data) - [Step 2. Generating NFTs](#step-2---generate-nfts) - [Step 3. Refactor Batches & Create MetaData](#step-3---refactor-batches--create-metadata) +- [Logic](#logic) + - [Logic JSON Schema](#logic-json-schema) - [Notes on Rarity and Weighted Variants](#notes-on-rarity-and-weighted-variants) - [.Blend File Rarity Example](#blend-file-rarity-examples) - [More complex Rarity Example](#more-complex-rarity-example) @@ -192,7 +194,7 @@ This repository contains three .blend example files that are compatable with Ble After you have formatted and organized your NFT collection in Blender to the rules outlined above in [Blender File Organization and Structure ](#blender-file-organization-and-structure) you can now go about generating your NFT collection. By the end of this process you will have a folder continaing the following: -1. NFT content files; images, animations, or 3D models in any format that you specify. +1. NFT media files; images, animations, or 3D models in any format that you specify. 2. Json metadata files; one fore each NFT content filem, formatted to the blockchain standard that you set. Before you get started, open the .blend of your NFT collection and open the side panel of the `Layout` tab so that Blend_My_NFTs is visible: @@ -236,7 +238,12 @@ Desktop is recommended for easy access, but any easily accessable directory will Screen Shot 2022-02-06 at 10 10 55 PM -8. Lastly click the `Create Data` button: + +7. Enable or Disable Logic with the checkbox `Enable Logic`. For more information on what affect this has on your NFT collection, see [Logic](#logic). + - If you enabled Logic, set the location of the Logic.json file you created in the ``Logic File`` field. Click on the file icon and navigate to the location of the json file. + - To create a Logic.json file, see the [Logic](#logic) section. + +9. Lastly click the `Create Data` button: Screen Shot 2022-02-06 at 10 12 37 PM @@ -371,7 +378,61 @@ After completeing the `Refactor Batches & Create MetaData` step, you should have Congratulations!! You now have a complete 3D NFT collection that is ready to upload to the blockchain of your choice! -## Common Issues and Problems +# Logic + +This section will go over the process of using rules to determine what combinations are excluded or included in your NFT collection. + +Logic is deterimened by a .json file that you manually create. For the purposes of this documentation, just think of JSON as a text file (.txt) that we can use to store information. You can name this file anything, but for this tutorial lets call it `Logic.json`. + +If you need help creating a JSON file, checkout this tutorial: [How to Create JSON File?](https://codebeautify.org/blog/how-to-create-json-file/) + +To learn more about JSON files and how to structure data read this article: [Working with JSON](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON) + +## Logic JSON Schema +If you'd like, copy and paste this template into the JSON file you created above: + +``` +{ + "Rule-1":{ + "Items-1": [ + "", + "", + ], + "Rule": "Never with", + "Items-2":[ + "", + "", + ] + }, + "Rule-2":{ + "Items-1": [ + "", + "", + ], + "Rule": "Only with", + "Items-2":[ + "", + "", + ] + } +} +``` +The above Logic.json template outlines the 2 possible rules; ``Never with`` and ``Only with``. + +- ``Rule-#`` A dictionary representing a single defined Rule of an NFT collection. There can be as many as you choose. Increment the ``#`` when you create a new rule. Note: The more rules you add the higher the chance a rule conflict may arise, and you may see Attribute and Variant behaviour that you do not desire. + - ``Items-1`` Created for each ``Rule-#``, a list of Attribute(s) or Variant(s) names. + - ```` The full name of an Attribute, as seen in your .blend file scene collection. All Variants of this attribute will be included in the Rule. + - ```` The full name of a Variant, as seen in your .blend file scene collection (include the order number and rarity percentage. e.g. ``Variant_1_0``) + - ``Rule`` The rule to govern the relation between ``Items-1`` and ``Items-2``. Has two possible values: ``Never with`` and ``Only with``. + - ``Never with`` If selected, ``Items-1`` will never appear if ``Items-2`` are selected. + - ``Only with`` If selected, ``Items-1`` will only appear when ``Items-2`` are selected. + - ``Items-2`` Created for each ``Rule-#``, a list of Attribute(s) or Variant(s) names. + - ```` The full name of an Attribute, as seen in your .blend file scene collection. All Variants of this attribute will be included in the Rule. + - ```` The full name of a Variant, as seen in your .blend file scene collection (include the order number and rarity percentage. e.g. ``Variant_1_0``) + +Now that you have a completed Logic.json file, you can now go back and complete [Step 1. Create Data](#step-1---create-nft-data)! + +# Common Issues and Problems - The most common issues people face are naming convention issues (See [Blender File Organization and Structure](#blender-file-organization-and-structure)). People often miss the naming convention on one or two collections and this typically throws up an error. The best way to resolve this is by reviewing the Blender File Organization and Structure standards and go through each collection in your Blender scene. diff --git a/__init__.py b/__init__.py index 5e6d32f..e0c01c7 100644 --- a/__init__.py +++ b/__init__.py @@ -13,7 +13,6 @@ bl_info = { import bpy from bpy.app.handlers import persistent - import os import importlib @@ -36,8 +35,6 @@ else: Batch_Refactorer, \ get_combinations - from .ui_Lists import UIList - # User input Property Group: class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup): @@ -105,6 +102,17 @@ class BMNFTS_PGT_MyProperties(bpy.types.PropertyGroup): # API Panel properties: apiKey: bpy.props.StringProperty(name="API Key", subtype='PASSWORD') + # Logic: + enableLogic: bpy.props.BoolProperty(name="Enable Logic") + logicFile: bpy.props.StringProperty( + name="Logic File", + description="Path where Logic.txt is located.", + default="", + maxlen=1024, + subtype="FILE_PATH" + ) + + def make_directories(save_path): Blend_My_NFTs_Output = os.path.join(save_path, "Blend_My_NFTs Output", "NFT_Data") batch_json_save_path = os.path.join(Blend_My_NFTs_Output, "Batch_Data") @@ -121,14 +129,16 @@ def make_directories(save_path): # Update NFT count: combinations: int = 0 -offset: int = 0 +recommended_limit: int = 0 @persistent def update_combinations(dummy1, dummy2): global combinations + global recommended_limit global offset - combinations = (get_combinations.get_combinations_from_scene()) - offset + combinations = (get_combinations.get_combinations_from_scene()) + recommended_limit = int(round(combinations/2)) redraw_panel() @@ -147,11 +157,14 @@ class createData(bpy.types.Operator): maxNFTs = bpy.context.scene.my_tool.collectionSize nftsPerBatch = bpy.context.scene.my_tool.nftsPerBatch save_path = bpy.path.abspath(bpy.context.scene.my_tool.save_path) + logicFile = bpy.path.abspath(bpy.context.scene.my_tool.logicFile) + enableRarity = bpy.context.scene.my_tool.enableRarity + enableLogic = bpy.context.scene.my_tool.enableLogic Blend_My_NFTs_Output, batch_json_save_path, nftBatch_save_path = make_directories(save_path) - DNA_Generator.send_To_Record_JSON(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity, Blend_My_NFTs_Output) + DNA_Generator.send_To_Record_JSON(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output) Batch_Sorter.makeBatches(nftName, maxNFTs, nftsPerBatch, save_path, batch_json_save_path) return {"FINISHED"} @@ -227,14 +240,13 @@ class BMNFTS_PT_CreateData(bpy.types.Panel): scene = context.scene mytool = scene.my_tool - layout.label(text=f"Maximum Number Of NFTs: {combinations}") - - row = layout.row() - layout.label(text="") - row = layout.row() row.prop(mytool, "nftName") + row = layout.row() + layout.label(text=f"Maximum Number Of NFTs: {combinations}") + layout.label(text=f"Recommended limit: {recommended_limit}") + row = layout.row() row.prop(mytool, "collectionSize") @@ -247,6 +259,13 @@ class BMNFTS_PT_CreateData(bpy.types.Panel): row = layout.row() row.prop(mytool, "enableRarity") + row = layout.row() + row.prop(mytool, "enableLogic") + + if bpy.context.scene.my_tool.enableLogic: + row = layout.row() + row.prop(mytool, "logicFile") + row = layout.row() self.layout.operator("create.data", icon='DISCLOSURE_TRI_RIGHT', text="Create Data") @@ -263,6 +282,9 @@ class BMNFTS_PT_GenerateNFTs(bpy.types.Panel): scene = context.scene mytool = scene.my_tool + row = layout.row() + layout.label(text="NFT Media files:") + row = layout.row() row.prop(mytool, "imageBool") if bpy.context.scene.my_tool.imageBool == True: @@ -323,19 +345,19 @@ class BMNFTS_PT_Documentation(bpy.types.Panel): row.operator("wm.url_open", text="Documentation", icon='URL').url = "https://github.com/torrinworx/Blend_My_NFTs" -# # Logic Panel: -# class BMNFTS_PT_LOGIC_Panel(bpy.types.Panel): -# bl_label = "Logic" -# bl_idname = "BMNFTS_PT_LOGIC_Panel" -# bl_space_type = 'VIEW_3D' -# bl_region_type = 'UI' -# bl_category = 'Blend_My_NFTs' -# -# def draw(self, context): -# layout = self.layout -# scene = context.scene -# mytool = scene.my_tool -# +# Logic Panel: +class BMNFTS_PT_LOGIC_Panel(bpy.types.Panel): + bl_label = "Logic" + bl_idname = "BMNFTS_PT_LOGIC_Panel" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_category = 'Blend_My_NFTs' + + def draw(self, context): + layout = self.layout + scene = context.scene + mytool = scene.my_tool + # # Materials Panel: # # class BMNFTS_PT_MATERIALS_Panel(bpy.types.Panel): @@ -385,26 +407,13 @@ classes = ( # Other panels: - # BMNFTS_PT_LOGIC_Panel, + BMNFTS_PT_LOGIC_Panel, # BMNFTS_PT_MATERIALS_Panel, # BMNFTS_PT_API_Panel, createData, exportNFTs, refactor_Batches, - - # UIList 1: - - # UIList.CUSTOM_OT_actions, - # UIList.CUSTOM_OT_addViewportSelection, - # UIList.CUSTOM_OT_printItems, - # UIList.CUSTOM_OT_clearList, - # UIList.CUSTOM_OT_removeDuplicates, - # UIList.CUSTOM_OT_selectItems, - # UIList.CUSTOM_OT_deleteObject, - # UIList.CUSTOM_UL_items, - # UIList.CUSTOM_PT_objectList, - # UIList.CUSTOM_PG_objectCollection, ) def register(): @@ -413,22 +422,12 @@ def register(): bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=BMNFTS_PGT_MyProperties) - # UIList1: - - # bpy.types.Scene.custom = bpy.props.CollectionProperty(type=UIList.CUSTOM_PG_objectCollection) - # bpy.types.Scene.custom_index = bpy.props.IntProperty() - - def unregister(): for cls in classes: bpy.utils.unregister_class(cls) del bpy.types.Scene.my_tool - # UIList 1: - - # del bpy.types.Scene.custom - # del bpy.types.Scene.custom_index if __name__ == '__main__': register() diff --git a/main/DNA_Generator.py b/main/DNA_Generator.py index 155a0d7..7e73967 100644 --- a/main/DNA_Generator.py +++ b/main/DNA_Generator.py @@ -11,8 +11,9 @@ import random import importlib from functools import partial -from . import Rarity_Sorter +from . import Rarity_Sorter, Logic importlib.reload(Rarity_Sorter) +importlib.reload(Logic) enableGeneration = False colorList = [] @@ -266,7 +267,7 @@ def returnData(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity): return listAllCollections, attributeCollections, attributeCollections1, hierarchy, possibleCombinations -def generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity): +def generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, logicFile, enableRarity, enableLogic): """ Returns batchDataDictionary containing the number of NFT combinations, hierarchy, and the DNAList. """ @@ -323,6 +324,13 @@ def generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity): possibleCombinations = maxNFTs DNAList = Rarity_Sorter.sortRarityWeights(hierarchy, listOptionVariant, DNAList, nftName, maxNFTs, nftsPerBatch, save_path, enableRarity) + if enableLogic: + print(f"{bcolors.OK} Logic is on. Rules listed in {logicFile} will be taken into account {bcolors.RESET}") + + DNAList = Logic.logicafyDNAList(DNAList, hierarchy, logicFile) + + + if len(DNAList) < maxNFTs: print(f"{bcolors.ERROR} \nWARNING: \n" f"You are seeing this warning because the program cannot generate {maxNFTs} NFTs with rarity enabled. " @@ -337,7 +345,7 @@ def generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity): return DataDictionary, possibleCombinations, DNAList -def send_To_Record_JSON(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity, Blend_My_NFTs_Output): +def send_To_Record_JSON(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity, enableLogic, logicFile, Blend_My_NFTs_Output): """ Creates NFTRecord.json file and sends "batchDataDictionary" to it. NFTRecord.json is a permanent record of all DNA you've generated with all attribute variants. If you add new variants or attributes to your .blend file, other scripts @@ -345,7 +353,7 @@ def send_To_Record_JSON(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity, repeate DNA. """ - DataDictionary, possibleCombinations, DNAList = generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, enableRarity) + DataDictionary, possibleCombinations, DNAList = generateNFT_DNA(nftName, maxNFTs, nftsPerBatch, save_path, logicFile, enableRarity, enableLogic) NFTRecord_save_path = os.path.join(Blend_My_NFTs_Output, "NFTRecord.json") diff --git a/main/Exporter.py b/main/Exporter.py index 9a7423d..c3514db 100644 --- a/main/Exporter.py +++ b/main/Exporter.py @@ -92,8 +92,9 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path for c in dnaDictionary: collection = dnaDictionary[c] if not enableGeneration: - bpy.data.collections[collection].hide_render = False - bpy.data.collections[collection].hide_viewport = False + if collection != '0': + bpy.data.collections[collection].hide_render = False + bpy.data.collections[collection].hide_viewport = False time_start_2 = time.time() @@ -167,8 +168,9 @@ def render_and_save_NFTs(nftName, maxNFTs, batchToGenerate, batch_json_save_path for i in dnaDictionary: coll = dnaDictionary[i] - for obj in bpy.data.collections[coll].all_objects: - obj.select_set(True) + if coll != '0': + for obj in bpy.data.collections[coll].all_objects: + obj.select_set(True) for obj in bpy.data.collections['Script_Ignore'].all_objects: obj.select_set(True) diff --git a/main/Logic.py b/main/Logic.py new file mode 100644 index 0000000..79502ef --- /dev/null +++ b/main/Logic.py @@ -0,0 +1,112 @@ +# Purpose: +# The purpose of this file is to add logic and rules to the DNA that are sent to the NFTRecord.json file in DNA_Generator.py + +import bpy +import os +import sys +import json +import random +import importlib + +from . import metaData + +importlib.reload(metaData) + +removeList = [".gitignore", ".DS_Store"] + + +def isAttorVar(hierarchy, items_List): + items_returned = {} + + for i in items_List: + for j in hierarchy: + if i == j: # If i is an Attribute, add all i Variants to dictionary. + items_returned[i] = list(hierarchy[j].keys()) + items_returned[i].append("Empty") + + for h in hierarchy[j]: + if h == i: # If i is a Variant, add i Variant and i's Attribute to dictionary. + items_returned[j] = [h] + + # Check if all variants in an attribute were included, if so, add "Empty" variant. + for i in items_returned: + if list(items_returned[i]) == list(hierarchy[i].keys()): + items_returned[i].append("Empty") + + return items_returned + +def getAttIndex(hierarchy, attribute): + attList = list(hierarchy.keys()) + index = attList.index(attribute) + return index + +def getVarNum(variant): + if variant == "Empty": + num = '0' + else: + num = variant.split("_")[1] + return num + +def items_to_num(hierarchy, items_List): + num_List = {} + for i in items_List: + variant_num_list = [] + + for j in items_List[i]: + variant_num_list.append(getVarNum(j)) + + num_List[getAttIndex(hierarchy, i)] = variant_num_list + + + return num_List + +def logicafyDNAList(DNAList, hierarchy, logicFile): + logicFile = json.load(open(logicFile)) + + LogicDNAList_deconstructed = [] + + for a in DNAList: + deconstructed_DNA = a.split("-") + for b in logicFile: + items_List1 = isAttorVar(hierarchy, logicFile[b]["Items-1"]) + items_List2 = isAttorVar(hierarchy, logicFile[b]["Items-2"]) + + print(items_List1) + print(items_List2) + + # Convert String Attributes to DNA Index number, and String Variants to Order number. Variant == 0 if Empty given. + num_List1 = items_to_num(hierarchy, items_List1) + # ^cannot go with: + num_List2 = items_to_num(hierarchy, items_List2) + + if logicFile[b]["Rule"] == "Never with": + rand_bool = random.getrandbits(1) == 0 + if rand_bool == 0: + for c in num_List2: + deconstructed_DNA[c] = '0' + + if rand_bool == 1: + for c in num_List1: + deconstructed_DNA[c] = '0' + + if logicFile[b]["Rule"] == "Only with": + for c in list(num_List2.keys()): + for d in num_List2[c]: + if deconstructed_DNA[c] not in d: + for e in list(num_List1.keys()): + deconstructed_DNA[e] = '0' + LogicDNAList_deconstructed.append(deconstructed_DNA) + + LogicDNAList = [] + for a in LogicDNAList_deconstructed: + reconstructed_DNA = "" + print(a) + for b in a: + num = "-" + str(b) + reconstructed_DNA += num + LogicDNAList.append(''.join(reconstructed_DNA.split('-', 1))) + return LogicDNAList + + +if __name__ == '__main__': + logicafyDNAList() diff --git a/ui_Lists/UIList.py b/ui_Lists/UIList.py deleted file mode 100644 index 0401ef9..0000000 --- a/ui_Lists/UIList.py +++ /dev/null @@ -1,357 +0,0 @@ -import bpy - -from bpy.props import (IntProperty, - BoolProperty, - StringProperty, - CollectionProperty, - PointerProperty) - -from bpy.types import (Operator, - Panel, - PropertyGroup, - UIList) - - -# ------------------------------------------------------------------- -# Operators -# ------------------------------------------------------------------- - -class CUSTOM_OT_actions(Operator): - """Move items up and down, add and remove""" - bl_idname = "custom.list_action" - bl_label = "List Actions" - bl_description = "Move items up and down, add and remove" - bl_options = {'REGISTER'} - - action: bpy.props.EnumProperty( - items=( - ('UP', "Up", ""), - ('DOWN', "Down", ""), - ('REMOVE', "Remove", ""), - ('ADD', "Add", ""))) - - def invoke(self, context, event): - scn = context.scene - idx = scn.custom_index - - try: - item = scn.custom[idx] - except IndexError: - pass - else: - if self.action == 'DOWN' and idx < len(scn.custom) - 1: - item_next = scn.custom[idx + 1].name - scn.custom.move(idx, idx + 1) - scn.custom_index += 1 - info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1) - self.report({'INFO'}, info) - - elif self.action == 'UP' and idx >= 1: - item_prev = scn.custom[idx - 1].name - scn.custom.move(idx, idx - 1) - scn.custom_index -= 1 - info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1) - self.report({'INFO'}, info) - - elif self.action == 'REMOVE': - info = 'Item "%s" removed from list' % (scn.custom[idx].name) - scn.custom_index -= 1 - scn.custom.remove(idx) - self.report({'INFO'}, info) - - if self.action == 'ADD': - if context.object: - item = scn.custom.add() - item.name = context.object.name - item.obj = context.object - scn.custom_index = len(scn.custom) - 1 - info = '"%s" added to list' % (item.name) - self.report({'INFO'}, info) - else: - self.report({'INFO'}, "Nothing selected in the Viewport") - return {"FINISHED"} - - -class CUSTOM_OT_addViewportSelection(Operator): - """Add all items currently selected in the viewport""" - bl_idname = "custom.add_viewport_selection" - bl_label = "Add Viewport Selection to List" - bl_description = "Add all items currently selected in the viewport" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - scn = context.scene - selected_objs = context.selected_objects - if selected_objs: - new_objs = [] - for i in selected_objs: - item = scn.custom.add() - item.name = i.name - item.obj = i - new_objs.append(item.name) - info = ', '.join(map(str, new_objs)) - self.report({'INFO'}, 'Added: "%s"' % (info)) - else: - self.report({'INFO'}, "Nothing selected in the Viewport") - return {'FINISHED'} - - -class CUSTOM_OT_printItems(Operator): - """Print all items and their properties to the console""" - bl_idname = "custom.print_items" - bl_label = "Print Items to Console" - bl_description = "Print all items and their properties to the console" - bl_options = {'REGISTER', 'UNDO'} - - reverse_order: BoolProperty( - default=False, - name="Reverse Order") - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def execute(self, context): - scn = context.scene - if self.reverse_order: - for i in range(scn.custom_index, -1, -1): - ob = scn.custom[i].obj - print("Object:", ob, "-", ob.name, ob.type) - else: - for item in scn.custom: - ob = item.obj - print("Object:", ob, "-", ob.name, ob.type) - return {'FINISHED'} - - -class CUSTOM_OT_clearList(Operator): - """Clear all items of the list""" - bl_idname = "custom.clear_list" - bl_label = "Clear List" - bl_description = "Clear all items of the list" - bl_options = {'INTERNAL'} - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def invoke(self, context, event): - return context.window_manager.invoke_confirm(self, event) - - def execute(self, context): - if bool(context.scene.custom): - context.scene.custom.clear() - self.report({'INFO'}, "All items removed") - else: - self.report({'INFO'}, "Nothing to remove") - return {'FINISHED'} - - -class CUSTOM_OT_removeDuplicates(Operator): - """Remove all duplicates""" - bl_idname = "custom.remove_duplicates" - bl_label = "Remove Duplicates" - bl_description = "Remove all duplicates" - bl_options = {'INTERNAL'} - - def find_duplicates(self, context): - """find all duplicates by name""" - name_lookup = {} - for c, i in enumerate(context.scene.custom): - name_lookup.setdefault(i.obj.name, []).append(c) - duplicates = set() - for name, indices in name_lookup.items(): - for i in indices[1:]: - duplicates.add(i) - return sorted(list(duplicates)) - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def execute(self, context): - scn = context.scene - removed_items = [] - # Reverse the list before removing the items - for i in self.find_duplicates(context)[::-1]: - scn.custom.remove(i) - removed_items.append(i) - if removed_items: - scn.custom_index = len(scn.custom) - 1 - info = ', '.join(map(str, removed_items)) - self.report({'INFO'}, "Removed indices: %s" % (info)) - else: - self.report({'INFO'}, "No duplicates") - return {'FINISHED'} - - def invoke(self, context, event): - return context.window_manager.invoke_confirm(self, event) - - -class CUSTOM_OT_selectItems(Operator): - """Select Items in the Viewport""" - bl_idname = "custom.select_items" - bl_label = "Select Item(s) in Viewport" - bl_description = "Select Items in the Viewport" - bl_options = {'REGISTER', 'UNDO'} - - select_all = BoolProperty( - default=False, - name="Select all Items of List", - options={'SKIP_SAVE'}) - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def execute(self, context): - scn = context.scene - idx = scn.custom_index - - try: - item = scn.custom[idx] - except IndexError: - self.report({'INFO'}, "Nothing selected in the list") - return {'CANCELLED'} - - obj_error = False - bpy.ops.object.select_all(action='DESELECT') - if not self.select_all: - name = scn.custom[idx].obj.name - obj = scn.objects.get(name, None) - if not obj: - obj_error = True - else: - obj.select_set(True) - info = '"%s" selected in Vieport' % (obj.name) - else: - selected_items = [] - unique_objs = set([i.obj.name for i in scn.custom]) - for i in unique_objs: - obj = scn.objects.get(i, None) - if obj: - obj.select_set(True) - selected_items.append(obj.name) - - if not selected_items: - obj_error = True - else: - missing_items = unique_objs.difference(selected_items) - if not missing_items: - info = '"%s" selected in Viewport' \ - % (', '.join(map(str, selected_items))) - else: - info = 'Missing items: "%s"' \ - % (', '.join(map(str, missing_items))) - if obj_error: - info = "Nothing to select, object removed from scene" - self.report({'INFO'}, info) - return {'FINISHED'} - - -class CUSTOM_OT_deleteObject(Operator): - """Delete object from scene""" - bl_idname = "custom.delete_object" - bl_label = "Remove Object from Scene" - bl_description = "Remove object from scene" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return bool(context.scene.custom) - - def invoke(self, context, event): - return context.window_manager.invoke_confirm(self, event) - - def execute(self, context): - scn = context.scene - selected_objs = context.selected_objects - idx = scn.custom_index - try: - item = scn.custom[idx] - except IndexError: - pass - else: - ob = scn.objects.get(item.obj.name) - if not ob: - self.report({'INFO'}, "No object of that name found in scene") - return {"CANCELLED"} - else: - bpy.ops.object.select_all(action='DESELECT') - ob.select_set(True) - bpy.ops.object.delete() - - info = ' Item "%s" removed from Scene' % (len(selected_objs)) - scn.custom_index -= 1 - scn.custom.remove(idx) - self.report({'INFO'}, info) - return {'FINISHED'} - - -# ------------------------------------------------------------------- -# Drawing -# ------------------------------------------------------------------- - -class CUSTOM_UL_items(UIList): - - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - obj = item.obj - custom_icon = "OUTLINER_OB_%s" % obj.type - split = layout.split(factor=0.3) - split.label(text="Index: %d" % (index)) - split.prop(obj, "name", text="", emboss=False, translate=False, icon=custom_icon) - - def invoke(self, context, event): - pass - - -class CUSTOM_PT_objectList(Panel): - """Adds a custom panel to the TEXT_EDITOR""" - bl_label = "UI List Test" - bl_idname = "BMNFTS_PT_uilisttest" - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = 'Blend_My_NFTs' - - def draw(self, context): - layout = self.layout - scn = bpy.context.scene - - rows = 2 - row = layout.row() - row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows) - - col = row.column(align=True) - col.operator("custom.list_action", icon='ADD', text="").action = 'ADD' - col.operator("custom.list_action", icon='REMOVE', text="").action = 'REMOVE' - col.separator() - col.operator("custom.list_action", icon='TRIA_UP', text="").action = 'UP' - col.operator("custom.list_action", icon='TRIA_DOWN', text="").action = 'DOWN' - - row = layout.row() - col = row.column(align=True) - row = col.row(align=True) - row.operator("custom.print_items", icon="LINENUMBERS_ON") - row = col.row(align=True) - row.operator("custom.clear_list", icon="X") - row.operator("custom.remove_duplicates", icon="GHOST_ENABLED") - - row = layout.row() - col = row.column(align=True) - row = col.row(align=True) - row.operator("custom.add_viewport_selection", icon="HAND") # LINENUMBERS_OFF, ANIM - - row = layout.row() - row = col.row(align=True) - row.operator("custom.delete_object", icon="X") - - -# ------------------------------------------------------------------- -# Collection -# ------------------------------------------------------------------- - -class CUSTOM_PG_objectCollection(PropertyGroup): - # name: StringProperty() -> Instantiated by default - obj: PointerProperty( - name="Object", - type=bpy.types.Object) diff --git a/ui_Lists/__init__.py b/ui_Lists/__init__.py deleted file mode 100644 index e69de29..0000000