From 03e0f31ab24a795c61cc472493ea4388bfef72a5 Mon Sep 17 00:00:00 2001 From: Finance Ape Club <102276365+FinanceApeClub@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:08:54 +0800 Subject: [PATCH 1/5] Modified main.js for adding two conditional rules of constructing layers to DNA --- src/main.js | 199 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 174 insertions(+), 25 deletions(-) diff --git a/src/main.js b/src/main.js index e9c08dcf2..e77f5943d 100644 --- a/src/main.js +++ b/src/main.js @@ -35,7 +35,8 @@ let hashlipsGiffer = null; const buildSetup = () => { if (fs.existsSync(buildDir)) { - fs.rmdirSync(buildDir, { recursive: true }); + // fs.rmdirSync => fs.rmSync due to fs.rmdirSync being degraded + fs.rmSync(buildDir, { recursive: true }); } fs.mkdirSync(buildDir); fs.mkdirSync(`${buildDir}/json`); @@ -68,7 +69,11 @@ const cleanName = (_str) => { return nameWithoutWeight; }; -const getElements = (path) => { +/* get elements for all image files under a path. + The elements are put into a flat array. + return [{id, name, filename, path, weight}...] +*/ +const getFlatElements = (path) => { return fs .readdirSync(path) .filter((item) => !/(^|\/)\.[^\/\.]/g.test(item)) @@ -86,26 +91,132 @@ const getElements = (path) => { }); }; +// get sub folders or file names under a path +const getSubDirs = (path) => { + return fs + .readdirSync(path) + .filter((item) => !/(^|\/)\.[^\/\.]/g.test(item)) +} + +/* get elements for a layer with sub folders. + The elements are grouped into an array for the files under each sub folder, + and an object of {group: folder_name, elements: element_array} is created for + a sub folder, thus such multiple objects are arrayed together. + return [{ group: folder_name, + elements:[{id, name, filename, path, weight}...]}...] +*/ +const getRecursiveElements = (path) => { + return getSubDirs(path) + .map((obj)=>({ + group: obj, + elements: fs.readdirSync(path+obj) + .map((i, index)=> { + return { + id: index, + name:cleanName(i), + filename: i, + path: `${path}${obj}/${i}`, + weight: getRarityWeight(i), + } + }) + })) +} + +/* get elements for a layer where some images are revealed only when a + specified layer's image is None while others revealed else. + The elements are grouped in two objects, one has a key named 'noneToReveal' + for elements which only reveal when the specified layer's image is None, the + other has a key named 'overlapReveal' for elements which reveal when the + specified layer's image is not None. + return {noneToReveal: [{id, name, filename, path, weight}...], + overlapReveal: [{id, name, filename, path, weight}...]} +*/ +const getNoneToRevealElements = (layerObj) => { + // get all files under the layer + let allFiles = fs.readdirSync(`${layersDir}/${layerObj.name}/`) + .filter((item) => !/(^|\/)\.[^\/\.]/g.test(item)) + + // get noneToRevealFiles/overlapRevealFiles by intersecting/differenting allFiles + // with/from noneToReveal files specified in the layerObj.options.noneToReveal + noneToRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) > -1}) + overlapRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) === -1}) + + // get noneToRevealElements and overlapRevealElements + noneToRevealElements = + noneToRevealFiles.map((i, index) => ({ + id: index, + name: cleanName(i), + filename: i, + path: `${layersDir}/${layerObj.name}/${i}`, + weight: getRarityWeight(i), + })) + + overlapRevealElements = + overlapRevealFiles.map((i, index) => ({ + id: index, + name: cleanName(i), + filename: i, + path: `${layersDir}/${layerObj.name}/${i}`, + weight: getRarityWeight(i), + })) + + // form a object and return + return {noneToReveal: noneToRevealElements, overlapReveal:overlapRevealElements} +} + + +/* get elements (single_element = {id, name, filename, path, weight}) for a layerObj in + three different conditions: + 1. (layerObj.options?.['subGroup'] == true) => layer has sub folders + 2. (layerObj.options?.['noneToReveal']?.length > 0) => images are grouped together in + different conditons whether a specified other layer image is None or not + 3. normal contdition: elements are gathered in a flat array [single_element ...] +*/ +const getElements = (layerObj) => { + let elements = + layerObj.options?.['subGroup'] == true + ? getRecursiveElements(`${layersDir}/${layerObj.name}/`) + : (layerObj.options?.['noneToReveal']?.length > 0 + ? getNoneToRevealElements(layerObj) + : getFlatElements(`${layersDir}/${layerObj.name}/`) + ) + return elements +} + +// set up layers in a standard way to be used for createDna(), constructDnaLayer() etc. const layersSetup = (layersOrder) => { const layers = layersOrder.map((layerObj, index) => ({ - id: index, - elements: getElements(`${layersDir}/${layerObj.name}/`), - name: - layerObj.options?.["displayName"] != undefined - ? layerObj.options?.["displayName"] - : layerObj.name, - blend: - layerObj.options?.["blend"] != undefined - ? layerObj.options?.["blend"] - : "source-over", - opacity: - layerObj.options?.["opacity"] != undefined - ? layerObj.options?.["opacity"] - : 1, - bypassDNA: - layerObj.options?.["bypassDNA"] !== undefined - ? layerObj.options?.["bypassDNA"] + id: index, + elements: + getElements(layerObj), //elements may be grouped in three different ways + name: + layerObj.options?.["displayName"] != undefined + ? layerObj.options?.["displayName"] + : layerObj.name, + blend: + layerObj.options?.["blend"] != undefined + ? layerObj.options?.["blend"] + : "source-over", + opacity: + layerObj.options?.["opacity"] != undefined + ? layerObj.options?.["opacity"] + : 1, + bypassDNA: + layerObj.options?.["bypassDNA"] !== undefined + ? layerObj.options?.["bypassDNA"] + : false, + subGroup: + layerObj.options?.["subGroup"] !== undefined + ? layerObj.options?.["subGroup"] : false, + linkLayer: + layerObj.options?.["linkLayer"] !== undefined + ? layerObj.options?.["linkLayer"] + : -1, + noneToReveal: + layerObj.options?.["noneToReveal"] !== undefined + ? layerObj.options?.["noneToReveal"] + : [], })); return layers; }; @@ -221,8 +332,28 @@ const drawElement = (_renderObject, _index, _layersLen) => { const constructLayerToDna = (_dna = "", _layers = []) => { let mappedDnaToLayers = _layers.map((layer, index) => { - let selectedElement = layer.elements.find( - (e) => e.id == cleanDna(_dna.split(DNA_DELIMITER)[index]) + /* As elements are grouped in three ways for each different kind of layer as specified in + layerConfigurations.layersOrder by the file src/config.js, elements are extracted for + each layer in their conresponding ways. + */ + // if the layer has sub_groups, the elements are nested in the sub_group + cur_elements = + layer.subGroup == true + ? (layer.elements.find(item => + item.group === cleanName(_dna.split(DNA_DELIMITER)[layer.linkLayer].split(':').pop()))).elements + : layer.elements + + // if some of images of the layer are only revealed when a specified layer image is None, + // the elements for these images are grouped in an object. + if (layer.noneToReveal.length > 0) { + cur_elements = + cleanName(_dna.split(DNA_DELIMITER)[layer.linkLayer].split(':').pop()) === 'None' + ? cur_elements['noneToReveal'] + : cur_elements['overlapReveal'] + } + + let selectedElement = cur_elements.find( + (e) => e.id == cleanDna(_dna.split(DNA_DELIMITER)[index]) ); return { name: layer.name, @@ -283,17 +414,35 @@ const createDna = (_layers) => { let randNum = []; _layers.forEach((layer) => { var totalWeight = 0; - layer.elements.forEach((element) => { + // if the layer has sub_groups, the elements are nested in the sub_group + cur_elements = + layer.subGroup == true + ? (layer.elements.find(item => + item.group === cleanName(randNum[layer.linkLayer].split(':').pop()))).elements + : layer.elements + // if some of images of the layer are only revealed when a specified layer image is None, + // the elements are grouped in an object. + if (layer.noneToReveal.length>0) { + if (cleanName(randNum[layer.linkLayer].split(':').pop())==='None') { + cur_elements = cur_elements['noneToReveal'] + } + else { + cur_elements = cur_elements['overlapReveal'] + } + } + + cur_elements.forEach((element) => { totalWeight += element.weight; }); + // number between 0 - totalWeight let random = Math.floor(Math.random() * totalWeight); - for (var i = 0; i < layer.elements.length; i++) { + for (var i = 0; i < cur_elements.length; i++) { // subtract the current weight from the random weight until we reach a sub zero value. - random -= layer.elements[i].weight; + random -= cur_elements[i].weight; if (random < 0) { return randNum.push( - `${layer.elements[i].id}:${layer.elements[i].filename}${ + `${cur_elements[i].id}:${cur_elements[i].filename}${ layer.bypassDNA ? "?bypassDNA=true" : "" }` ); From c698bf15f07d6ce32d5779ea0bc7766c70495e2e Mon Sep 17 00:00:00 2001 From: Finance Ape Club <102276365+FinanceApeClub@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:12:39 +0800 Subject: [PATCH 2/5] Modified config.js for adding two conditional rules of constructing layers to DNA --- src/config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/config.js b/src/config.js index c46d867a0..b8c02a393 100644 --- a/src/config.js +++ b/src/config.js @@ -33,6 +33,22 @@ const layerConfigurations = [ { name: "Shine" }, { name: "Bottom lid" }, { name: "Top lid" }, + + // { name: "Fur" }, + // { name: "Hair", + // options: { + // subGroup: true, // existance of sub folder corresponding to each kind of fur + // linkLayer: 7, // layer 7 of Fur + // }, + // }, + // { name: "Hats", + // options: { + // noneToReveal: ['Hat_type1.png', 'Hat_type2.png'], // list of hats + // // revealed only when Hair is None + // linkLayer: 8, // layer 8 of Hair + // }, + // }, + ], }, ]; From 35a7b1bec759d5c44a2dda53297f3f645efec401 Mon Sep 17 00:00:00 2001 From: Finance Ape Club <102276365+FinanceApeClub@users.noreply.github.com> Date: Thu, 26 May 2022 11:19:12 +0800 Subject: [PATCH 3/5] add rarities to options object --- src/config.js | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/config.js b/src/config.js index b8c02a393..42b16a49b 100644 --- a/src/config.js +++ b/src/config.js @@ -34,32 +34,47 @@ const layerConfigurations = [ { name: "Bottom lid" }, { name: "Top lid" }, - // { name: "Fur" }, - // { name: "Hair", + // { name: "Fur", // options: { - // subGroup: true, // existance of sub folder corresponding to each kind of fur - // linkLayer: 7, // layer 7 of Fur + // rarities: [90, [10,12,6,7,21,18]], // }, // }, + // { name: "Hair", + // options: { + // subGroup: true, // existance of sub folder corresponding to each kind of fur + // linkLayer: 7, // layer 7 of Fur + // rarities: [ + // [90, []], + // ], + // noResetRarities: false, + // }, + // }, + // { name: "Mouth", + // options: { + // rarities: [80, []], + // } + // }, // { name: "Hats", // options: { - // noneToReveal: ['Hat_type1.png', 'Hat_type2.png'], // list of hats - // // revealed only when Hair is None + // noneToReveal: ['Hat_type1.png', 'Hat_type2.png'], // list of hats + // // revealed only when Hair is None // linkLayer: 8, // layer 8 of Hair + // rarities: [100, [], []], // }, // }, - ], }, ]; + +const src_none_file = `${basePath}/utils/None.png`; const shuffleLayerConfigurations = false; const debugLogs = false; const format = { - width: 512, - height: 512, + width: 2020, + height: 2020, smoothing: false, }; @@ -123,6 +138,7 @@ module.exports = { background, uniqueDnaTorrance, layerConfigurations, + src_none_file, rarityDelimiter, preview, shuffleLayerConfigurations, From 24f5e32b9dcfcb88c2574fc0f7e4292fc344d7c2 Mon Sep 17 00:00:00 2001 From: Finance Ape Club <102276365+FinanceApeClub@users.noreply.github.com> Date: Thu, 26 May 2022 11:29:48 +0800 Subject: [PATCH 4/5] modify for set_rarities --- src/main.js | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main.js b/src/main.js index e77f5943d..7b887c29b 100644 --- a/src/main.js +++ b/src/main.js @@ -138,9 +138,23 @@ const getNoneToRevealElements = (layerObj) => { // get noneToRevealFiles/overlapRevealFiles by intersecting/differenting allFiles // with/from noneToReveal files specified in the layerObj.options.noneToReveal - noneToRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) > -1}) - overlapRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) === -1}) - + // noneToRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) > -1}) + // overlapRevealFiles = allFiles.filter( (val) => { return layerObj.options.noneToReveal.indexOf(val) === -1}) + let noneToRevealFiles = [] + let overlapRevealFiles = [] + let pureNoneToReveail = layerObj.options.noneToReveal.map(item=>item.split('.').shift()) + + // console.log(pureNoneToReveail) + + allFiles.forEach (fl => { + if (pureNoneToReveail.includes(fl.split(rarityDelimiter).shift().split('.').shift())) { + noneToRevealFiles.push(fl) + } + else { + overlapRevealFiles.push(fl) + } + }) + // get noneToRevealElements and overlapRevealElements noneToRevealElements = noneToRevealFiles.map((i, index) => ({ @@ -160,7 +174,7 @@ const getNoneToRevealElements = (layerObj) => { weight: getRarityWeight(i), })) - // form a object and return + // form a object and return return {noneToReveal: noneToRevealElements, overlapReveal:overlapRevealElements} } @@ -245,12 +259,12 @@ const addMetadata = (_dna, _edition) => { name: `${namePrefix} #${_edition}`, description: description, image: `${baseUri}/${_edition}.png`, - dna: sha1(_dna), + // dna: sha1(_dna), edition: _edition, - date: dateTime, + // date: dateTime, ...extraMetadata, attributes: attributesList, - compiler: "HashLips Art Engine", + // compiler: "HashLips Art Engine", }; if (network == NETWORK.sol) { tempMetadata = { @@ -284,6 +298,9 @@ const addMetadata = (_dna, _edition) => { const addAttributes = (_element) => { let selectedElement = _element.layer.selectedElement; + if (selectedElement.name === 'None' | selectedElement.name === 'none' ) { + return + } attributesList.push({ trait_type: _element.layer.name, value: selectedElement.name, @@ -414,7 +431,8 @@ const createDna = (_layers) => { let randNum = []; _layers.forEach((layer) => { var totalWeight = 0; - // if the layer has sub_groups, the elements are nested in the sub_group + // if the layer has sub_groups, the elements are nested in the sub_group + randNum[layer.linkLayer] cur_elements = layer.subGroup == true ? (layer.elements.find(item => From af9dc67c6dee37a1e31cceffef6e88c847ebc327 Mon Sep 17 00:00:00 2001 From: Finance Ape Club <102276365+FinanceApeClub@users.noreply.github.com> Date: Thu, 26 May 2022 12:29:57 +0800 Subject: [PATCH 5/5] Add util of set_rarities (#1) * create add_rarities * add None.png for set_rarities use * add set_rarities to scripts object * add content to Usage and Utils * add rarities to options object * modify for set_rarities --- README.md | 65 +++++++++++++ package.json | 3 +- utils/None.png | Bin 0 -> 15910 bytes utils/set_rarities.js | 209 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 utils/None.png create mode 100644 utils/set_rarities.js diff --git a/README.md b/README.md index 98a393c2e..79afc3860 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,63 @@ const layerConfigurations = [ ]; ``` +In addtion, a couple of conditional rules of constructing layers to DNA are applied to adapt the situations as follows. An example of layerConfigurations in _src/config.js_ is listed below to explain these situations. + +```js +const layerConfigurations = [ + { + growEditionSizeTo: 5, + layersOrder: [ + { name: "Background" }, + { name: "Eyeball" }, + { name: "Eye color" }, + { name: "Iris" }, + { name: "Shine" }, + { name: "Bottom lid" }, + { name: "Top lid" }, + { name: "Fur", + options: { + rarities: [90, [10,12,6,7,21,18]], + }, + }, + { name: "Hair", + options: { + subGroup: true, // existance of sub folder corresponding to each kind of fur + linkLayer: 7, // layer 7 of Fur + rarities: [ + [90, []], + ], + noResetRarities: false, + }, + }, + { name: "Mouth", + options: { + rarities: [80, []], + } + }, + { name: "Hats", + options: { + noneToReveal: ['Hat_type1.png', 'Hat_type2.png'], // list of hats + // revealed only when Hair is None + linkLayer: 8, // layer 8 of Hair + rarities: [100, [], []], + }, + }, + ], + }, +]; + +``` +1. If a layer folder has its sub-folders, each sub-folder contains image files which only apply to a certain trait (image file) of another specified layer, e.g. "_Hair_" layer has 'White', 'Yellow,' 'Golden' sub-folders, the hair-style files inside the 'White' sub-folder can only apply to 'White' fur trait (White.png) of "_Fur_" layer. In this case, the config.js file has to specify an object _{options: {subGroup: true, linkeLayer: 7}}_ in the "_Hair_" layer, where _{subGroup: true}_ means that this layer has sub-folders, and _{linkeLayer: 7}_ means that the linked layer is the _7th_ layer (layer index starts from 0) of "_Fur_". ## Please note that the following conventions must be complied: 1) all the sub-folders must have their corresponding image files in the their linkLayer, and vice versa; 2) each sub-folder name must be exactly the same as its corresponding linkLayer image file name without its extension. e.g. if "_Fur_" layer has three image files: 'White.png', 'Yellow.png' and 'Golden.png', "_Hair_" layer must have and can only have three sub-folders of 'White', 'Yellow' and 'Golden'. +2. Some of image files for traits in a layer only apply/reveal when the trait of a specified layer is None (None.png blank image file), and the rest files apply when the trait of the specified layer is not None. This condition is referred to as '**NoneToReveal**' hereafter. As an example shown above, layer "_Hats_" specifies in its options object _{noneToReveal: ['Hat_type1.png', 'Hat_type2.png'], linkLayer: 8}_, which means if the trait of "_Hair_" of the linkLayer 8 (layer index starts from 0) is None, "_Hats_" layer can only choose from the list of _['Hat_type1.png', 'Hat_type2.png']_, else all the rest files under "_Hats_" folder except those in the list. + +A util of utils/set_rarities.js can be used to set rarity weights for each trait file automatically by specifying _{rarities: {number, []}_ in its options object of each layer: +1) If no rarities object specified, all the traits are set the same weight evenly, and None is not allowed; layers "_Background_", "_Eyeball_", "_Eye color_", "_Iris_", "_Shine_", "_Bottom lid_" and "_Top lid_" are the case; +2) Rarity weights are configured in an object _{rarities: {number, []}_, where _number_% is the sum of percentage weights of all traits except None trait, _(100-number)_% is the percentage weight of None trait; _[]_ after _number_ is a weight list corresponding to its trait files in alphabetical order. As an example shown in the above code, layer "_Fur_" rarities is to be set as _{options: {rarities: [90, [10,12,6,7,21,18]],}_, where 90 means the sum of all traits except None trait is 90%, and _[10,12,6,7,21,18]_ means there are 6 trait files excluding None, and these 6 trait files are assigned weights propotionally as specified in the list. Please note if the list is not empty, the number of the list elements must be the same as the number of trait files. If the list is empty, the available traits are set the same weight evenly, layer "_Mouth_" is the case. +3) For layers with sub-folders, such a format {rarities: _[[number, []], ...]}_ is applied, one _[number, []]_ is specified for each sub-folder. If only one _[number, []]_ is specified in the format _{rarities: [[numbler,[]],]}_, the specified _[number, []]_ will apply to all the sub-folders, layer _"Hair"_ is the case. +4) For the '**NoneToReveal**' condition as mentioned above, formart _{rarities: [number, [], []]}_ is applied, where _number_% is the sum of percentage weights of all traits except the noneToReveal list, the two sub lists are sequentially the weight lists for the traits excluding noneToReveal, and those in noneToReveal list. Here again an empty list means that weights are set evenly. +5) _{options: {noResetRarities: true}}_ may be specified for a layer in the case that rarity weights have already assigned for this layer, and no more update is needed. + Here is a list of the different blending modes that you can optionally use. ```js @@ -247,6 +304,14 @@ Create a preview image collage of your collection, run: npm run preview ``` +### Set specified rarities to image files + +Set rarities specified in src/config.js to image files , run: + +```sh +npm run set_rarities +``` + ### Generate pixelated images from collection In order to convert images into pixelated images you would need a list of images that you want to convert. So run the generator first. diff --git a/package.json b/package.json index 141abf44e..2502b1ca9 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "pixelate": "node utils/pixelate.js", "update_info": "node utils/update_info.js", "preview_gif": "node utils/preview_gif.js", - "generate_metadata": "node utils/generate_metadata.js" + "generate_metadata": "node utils/generate_metadata.js", + "set_rarities": "node utils/set_rarities.js" }, "author": "Daniel Eugene Botha (HashLips)", "license": "MIT", diff --git a/utils/None.png b/utils/None.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7bdf98ccd09a95cf4ac492099c4229e32b597a GIT binary patch literal 15910 zcmeIuF-ikr5C+g$gcugKv9#24SghHg+bkmKW05ikNV#a0&Q?g5G7t}7W$g{LwNzWX zB>Nv@!yB09gMs
|@