Merge pull request #64 from torrinworx/DNA-Logic

Dna logic
pull/67/head
Torrin Leonard 2022-03-08 21:46:48 -05:00 zatwierdzone przez GitHub
commit d698363e7b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
7 zmienionych plików z 241 dodań i 416 usunięć

Wyświetl plik

@ -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
<img width="428" alt="Screen Shot 2022-02-06 at 10 10 55 PM" src="https://user-images.githubusercontent.com/82110564/152718643-d1580692-eac4-47bf-a41a-0e4748517b0d.png">
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:
<img width="425" alt="Screen Shot 2022-02-06 at 10 12 37 PM" src="https://user-images.githubusercontent.com/82110564/152718783-8ee0d72a-9223-4168-9664-c55b9cb6d84f.png">
@ -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": [
"<attribute name>",
"<variant name>",
],
"Rule": "Never with",
"Items-2":[
"<attribute name>",
"<variant name>",
]
},
"Rule-2":{
"Items-1": [
"<attribute name>",
"<variant name>",
],
"Rule": "Only with",
"Items-2":[
"<attribute name>",
"<variant name>",
]
}
}
```
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.
- ``<attribute name>`` 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.
- ``<variant name>`` 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.
- ``<attribute name>`` 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.
- ``<variant name>`` 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.

Wyświetl plik

@ -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()

Wyświetl plik

@ -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")

Wyświetl plik

@ -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)

112
main/Logic.py 100644
Wyświetl plik

@ -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()

Wyświetl plik

@ -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)