Skip to content

Commit 914f1ba

Browse files
authoredNov 18, 2021
Flp improvements (metaplex-foundation#1009)
* Add token sender * Add dnp, premade customs, and customized probabilities to generative images * Better handling for token accounts * Fix for punch ticket showing up even if you didnt win. * Add psd layer generator and fix stuff * Remove env and fix lock * Scew linting * Add changelog
1 parent dd21d74 commit 914f1ba

File tree

8 files changed

+241
-42
lines changed

8 files changed

+241
-42
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ rust/test/nft-candy-machine.js
66
dist/
77
lib/
88
deploy/
9+
venv/
10+
js/packages/cli/assets/
911
docs/lockup-ui/
1012
.DS_Store
1113
*~

‎CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ incremented for features.
2424
- Cleanup and adding more CI testing.
2525
- mint tokens. useful for testing devnet and pre-minting on mainnet
2626
- updated cache structure, now also includes the image URL
27+
- DNP for image generation in the Candy Machine CLI
28+
- If x then y probability for image generation in the Candy Machine CLI
29+
- Ability to add premade customs to your image generation in the candy machine CLI
30+
- Ability to use PSD instead of PNGs in the candy machine CLI
2731

2832
### Fixes
2933

@@ -36,7 +40,7 @@ incremented for features.
3640
- Fix #830 - secondary sale flagging
3741
- Fixes AUCTION_SIZE const
3842
- Fixes #930
39-
- Fixes Token Metadata Test Harness and lints rust code.
43+
- Fixes Token Metadata Test Harness and lints rust code.
4044
- When a token account already exists, punchTicket should not blow up in punch_and_refund_all_outstanding.
4145
- Fix for punch ticket showing up as a button if you have an FLP presale token but didn't win. You should see Withdrawal.
4246
- Fix lint issues preventing CI from passing

‎js/packages/cli/README.md

+44-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ The following file will be generated (based off of `example-traits`):
2121
"description": "",
2222
"creators": [],
2323
"collection": {},
24+
"premadeCustoms": [
25+
{
26+
"background": "blue.png",
27+
"eyes": "egg-eyes.png",
28+
"face": "cyan-face.png",
29+
"mouth": "block-mouth.png"
30+
},
31+
{
32+
"background": "red.png",
33+
"eyes": "egg-eyes.png",
34+
"face": "cyan-face.png",
35+
"mouth": "block-mouth.png"
36+
}
37+
],
38+
"dnp": {
39+
"background": {
40+
"blue.png": {
41+
"eyes": ["egg-eyes.png", "heart-eyes.png"]
42+
}
43+
}
44+
},
2445
"breakdown": {
2546
"background": {
2647
"blue.png": 0.04,
@@ -45,7 +66,13 @@ The following file will be generated (based off of `example-traits`):
4566
"star-eyes.png": 0.56
4667
},
4768
"face": {
48-
"cyan-face.png": 0.07,
69+
"cyan-face.png": {
70+
"baseValue": 0.07,
71+
"background": {
72+
"blue.png": 0.9,
73+
"brown.png": 0.1
74+
}
75+
},
4976
"dark-green-face.png": 0.04,
5077
"flesh-face.png": 0.03,
5178
"gold-face.png": 0.11,
@@ -78,6 +105,22 @@ ts-node cli create_generative_art -c <configuration_file_location> -n <number_of
78105

79106
5. This will create an `assets` folder, with a set of the JSON and PNG files to make it work!
80107

108+
6. Note: Need to use a PSD instead of pngs? That's fine. You can have a PSD with two levels of layers - a top level like HEAD and then sublevel like "White Hat", "Black Hat", etc. Then you can create a traits
109+
configuration where the hash of each feature is named "HEAD" and then each layer shows up as a key with
110+
a probability, instead of PNGs. Then you can use the -o ability of the create_generative_art app
111+
to output just JSON files to the assets folder AND a whole array of arrays json to be used by the psd_layer_generator.py app to actually generate the images.
112+
113+
NOTE: Do not forget that "No Traits" is a special reserved key for this app.
114+
115+
To use the psd_layer_generator, do:
116+
117+
```
118+
python psd_layer_generator.py -p ./traits.psd -o assets/ -t sets.json -e "FINISHERS" -n 0
119+
```
120+
121+
Where sets is your output from the create generate command, and -e is an optional layer you toggle to true
122+
for every item. You may need this if you have extra finishing touches.
123+
81124
## assets folder
82125

83126
- Folder with file pairs named with incrementing integer numbers starting from 0.png and 0.json
+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
#python janeApe.py
2+
#Created by Conor Holds | twitter.com/ConorHolds
3+
4+
#### NOTES ABOUT THIS SCRIPT ####
5+
#Requires a PSD file containing all possible variants of the art you want to generate.
6+
# All layers in the PSD file should be hidden before running this script.
7+
8+
9+
#### PACKAGE IMPORTS ####
10+
from psd_tools import PSDImage
11+
import random
12+
import csv
13+
import os
14+
import time
15+
import json
16+
import sys, getopt
17+
18+
from psd_tools.psd.image_resources import LayerGroupEnabledIDs
19+
####~~~ END OF PACKAGE IMPORTS ~~~####
20+
21+
22+
#### GENERATIVE ART FUNCTIONS ####
23+
24+
def showLayers(psd, layer, sub_layer=None):
25+
print("Setting layer to visible - " + str(layer), str(sub_layer))
26+
27+
group_layer = next(filter(lambda x: x.name == layer, psd))
28+
if group_layer.is_group():
29+
if sub_layer:
30+
group_child_layer = next(filter(lambda x: x.name == sub_layer, group_layer))
31+
group_child_layer.visible = True
32+
else:
33+
for layer in group_layer:
34+
layer.visible = True
35+
group_layer.visible = True
36+
37+
return psd
38+
39+
40+
def hideLayers(psd,layer, sub_layer=None):
41+
group_layer = next(filter(lambda x: x.name == layer, psd))
42+
43+
if group_layer.is_group():
44+
if sub_layer:
45+
group_child_layer = next(filter(lambda x: x.name == sub_layer, group_layer))
46+
group_child_layer.visible = False
47+
else:
48+
for layer in group_layer:
49+
layer.visible = False
50+
group_layer.visible = False
51+
52+
print("Setting layer to visible - " + str(layer), str(sub_layer))
53+
return psd
54+
55+
56+
def saveImage(psd, individualImageNumber, outputFolder):
57+
image = psd.compose(force = True)
58+
nameForImage = str(individualImageNumber) + ".png"
59+
pathForImage = os.path.join(outputFolder, nameForImage)
60+
image.save(pathForImage)
61+
return
62+
63+
64+
##The main image creation step##
65+
def createImages (psd, traits_file, output_dir, effects_layer, offset):
66+
f = open(traits_file)
67+
68+
data = json.load(f)
69+
individual_image_number = offset
70+
71+
for val in data[0].items():
72+
if val[0] != "id":
73+
psd = hideLayers(psd, val[0], None)
74+
75+
for data_set in data[offset:]:
76+
print("Starting to create image number: " + str(individual_image_number))
77+
78+
for val in data_set.items():
79+
if val[0] != "id" and val[1] != "No Traits":
80+
psd = showLayers(psd, val[0], val[1])
81+
if effects_layer:
82+
psd = showLayers(psd, effects_layer, None)
83+
84+
saveImage(psd, individual_image_number, output_dir)
85+
86+
for val in data_set.items():
87+
if val[0] != "id":
88+
psd = hideLayers(psd, val[0], None)
89+
if effects_layer:
90+
psd = hideLayers(psd, effects_layer, None)
91+
92+
print("Finished creating image number: " + str(individual_image_number))
93+
print("-----------------------------------------------------")
94+
individual_image_number += 1
95+
96+
####~~~ END OF GENERATIVE ART FUNCTIONS ~~~####
97+
98+
99+
#### THE MAIN PROGRAM ####
100+
def main(photoshop_file, output_dir, traits_file, effects_layer, offset):
101+
psd = PSDImage.open(photoshop_file)
102+
if not os.path.exists(output_dir):
103+
os.makedirs(output_dir)
104+
createImages (psd, traits_file, output_dir, effects_layer, offset)
105+
return print('Program Completed')
106+
107+
####~~~ END THE MAIN PROGRAM ~~~####
108+
109+
110+
#### RUN THE PROGRAM ####
111+
if __name__ == "__main__":
112+
argv = sys.argv[1:]
113+
photoshop_file = ''
114+
output_dir = ''
115+
traits_file = ''
116+
offset = 0
117+
effects_layer = None
118+
if len(argv) == 0:
119+
print('psd_layer_generator.py -p <psd> -o <output directory> -t <traits json> -e <effects layer> -n <offset>')
120+
sys.exit(2)
121+
try:
122+
opts, args = getopt.getopt(argv,"hp:o:t:e:n:")
123+
except getopt.GetoptError:
124+
print('psd_layer_generator.py -p <psd> -o <output directory> -t <traits json> -e <effects layer> -n <offset>')
125+
sys.exit(2)
126+
for opt, arg in opts:
127+
if opt == '-h':
128+
print('psd_layer_generator.py -p <psd> -o <output directory> -t <traits json> -e <effects layer> -n <offset>')
129+
sys.exit()
130+
elif opt in ("-p"):
131+
photoshop_file = arg
132+
elif opt in ("-o"):
133+
output_dir = arg
134+
elif opt in ("-t"):
135+
traits_file = arg
136+
elif opt in ("-e"):
137+
effects_layer = arg
138+
elif opt in ("-n"):
139+
offset = int(arg)
140+
main(photoshop_file, output_dir, traits_file, effects_layer, offset)

‎js/packages/cli/src/candy-machine-cli.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -837,8 +837,12 @@ programCommand('create_generative_art')
837837
'Location of the traits configuration file',
838838
'./traits-configuration.json',
839839
)
840+
.option(
841+
'-o, --output-location <string>',
842+
'If you wish to do image generation elsewhere, skip it and dump randomized sets to file',
843+
)
840844
.action(async (directory, cmd) => {
841-
const { numberOfImages, configLocation } = cmd.opts();
845+
const { numberOfImages, configLocation, outputLocation } = cmd.opts();
842846

843847
log.info('Loaded configuration file');
844848

@@ -851,9 +855,14 @@ programCommand('create_generative_art')
851855
log.info('JSON files have been created within the assets directory');
852856

853857
// 2. piecemeal generate the images
854-
await createGenerativeArt(configLocation, randomSets);
858+
if (!outputLocation) {
859+
await createGenerativeArt(configLocation, randomSets);
860+
log.info('Images have been created successfully!');
861+
} else {
862+
fs.writeFileSync(outputLocation, JSON.stringify(randomSets));
855863

856-
log.info('Images have been created successfully!');
864+
log.info('Traits written!');
865+
}
857866
});
858867

859868
function programCommand(name: string) {

‎js/packages/cli/src/helpers/metadata.ts

+1-22
Original file line numberDiff line numberDiff line change
@@ -50,27 +50,6 @@ export async function createMetadataFiles(
5050

5151
if (!_.some(randomizedSets, randomizedSet)) {
5252
randomizedSets.push(randomizedSet);
53-
54-
const metadata = getMetadata(
55-
name,
56-
symbol,
57-
numberOfFilesCreated,
58-
creators,
59-
description,
60-
seller_fee_basis_points,
61-
randomizedSet,
62-
collection,
63-
);
64-
65-
try {
66-
await writeFile(
67-
`${ASSETS_DIRECTORY}/${numberOfFilesCreated}.json`,
68-
JSON.stringify(metadata),
69-
);
70-
} catch (err) {
71-
log.error(`${numberOfFilesCreated} failed to get created`, err);
72-
}
73-
7453
numberOfFilesCreated += 1;
7554
}
7655
}
@@ -81,7 +60,7 @@ export async function createMetadataFiles(
8160
const metadata = getMetadata(
8261
name,
8362
symbol,
84-
numberOfFilesCreated,
63+
i,
8564
creators,
8665
description,
8766
seller_fee_basis_points,

‎js/packages/cli/src/helpers/various.ts

+36-14
Original file line numberDiff line numberDiff line change
@@ -32,43 +32,64 @@ export function shuffle(array) {
3232
return array;
3333
}
3434

35+
export const assertValidBreakdown = breakdown => {
36+
const total = Object.values(breakdown).reduce(
37+
(sum: number, el: number) => (sum += el),
38+
0,
39+
);
40+
if (total > 101 || total < 99) {
41+
console.log(breakdown);
42+
throw new Error('Breakdown not within 1% of 100! It is: ' + total);
43+
}
44+
};
45+
3546
export const generateRandomSet = (breakdown, dnp) => {
3647
let valid = true;
3748
let tmp = {};
3849

3950
do {
4051
valid = true;
4152
const keys = shuffle(Object.keys(breakdown));
53+
keys.forEach(attr => {
54+
const breakdownToUse = breakdown[attr];
55+
56+
const formatted = Object.keys(breakdownToUse).reduce((f, key) => {
57+
if (breakdownToUse[key]['baseValue']) {
58+
f[key] = breakdownToUse[key]['baseValue'];
59+
} else {
60+
f[key] = breakdownToUse[key];
61+
}
62+
return f;
63+
}, {});
64+
65+
assertValidBreakdown(formatted);
66+
const randomSelection = weighted.select(formatted);
67+
tmp[attr] = randomSelection;
68+
});
69+
4270
keys.forEach(attr => {
4371
let breakdownToUse = breakdown[attr];
72+
4473
keys.forEach(otherAttr => {
4574
if (
4675
tmp[otherAttr] &&
4776
typeof breakdown[otherAttr][tmp[otherAttr]] != 'number' &&
4877
breakdown[otherAttr][tmp[otherAttr]][attr]
4978
) {
79+
breakdownToUse = breakdown[otherAttr][tmp[otherAttr]][attr];
80+
5081
console.log(
5182
'Because this item got attr',
5283
tmp[otherAttr],
5384
'we are using different probabilites for',
5485
attr,
5586
);
56-
breakdownToUse = breakdown[otherAttr][tmp[otherAttr]][attr];
57-
}
58-
});
5987

60-
const formatted = Object.keys(breakdownToUse).reduce((f, key) => {
61-
if (breakdownToUse[key]['baseValue']) {
62-
f[key] = breakdownToUse[key]['baseValue'];
63-
} else {
64-
f[key] = breakdownToUse[key];
88+
assertValidBreakdown(breakdownToUse);
89+
const randomSelection = weighted.select(breakdownToUse);
90+
tmp[attr] = randomSelection;
6591
}
66-
return f;
67-
}, {});
68-
69-
const randomSelection = weighted.select(formatted);
70-
71-
tmp[attr] = randomSelection;
92+
});
7293
});
7394

7495
Object.keys(tmp).forEach(attr1 => {
@@ -224,6 +245,7 @@ export const getMetadata = (
224245
value: path.parse(attrs[prop]).name,
225246
});
226247
}
248+
227249
return {
228250
name: `${name}${index + 1}`,
229251
symbol,

‎js/packages/fair-launch/.env

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ REACT_APP_SOLANA_NETWORK=mainnet-beta
44
REACT_APP_SOLANA_RPC_HOST=https://api.mainnet-beta.solana.com
55

66
# Phase 1
7-
REACT_APP_FAIR_LAUNCH_ID=
7+
REACT_APP_FAIR_LAUNCH_ID=

0 commit comments

Comments
 (0)
Please sign in to comment.