From ea8e94d696cc8c5a6699d6f493bdfc98d5d52497 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Sun, 13 Oct 2024 01:42:09 -0700 Subject: [PATCH 01/13] adding dynamic_prompts script --- scripts/dynamic-prompts/dynamic_prompts.js | 544 +++++++++++++++++++++ scripts/dynamic-prompts/metadata.json | 6 + 2 files changed, 550 insertions(+) create mode 100644 scripts/dynamic-prompts/dynamic_prompts.js create mode 100644 scripts/dynamic-prompts/metadata.json diff --git a/scripts/dynamic-prompts/dynamic_prompts.js b/scripts/dynamic-prompts/dynamic_prompts.js new file mode 100644 index 0000000..71f72f6 --- /dev/null +++ b/scripts/dynamic-prompts/dynamic_prompts.js @@ -0,0 +1,544 @@ +//@api-1.0 +// dynamic prompts +// author zanshinmu +// v3.5 +/** + * Documentation for "Dynamic Prompts" Script (Version 3.5) for "Draw Things" + * + * This script generates dynamic prompts for the "Draw Things" application. Customize it to enhance your creative experience. + * + * Modifying Categories: + * - Categories are thematic elements like 'Locale', 'Adjective', etc. + * - To add a new category: Include it in the 'categories' object (e.g., "Futuristic": ["cybernetic", "AI-driven"]). + * - To modify an existing category: Add or remove elements directly in the category's list. + * + * Dynamic Prompt Strings: + * - The Prompts string structures your generated prompts. + * - Use curly braces {} to include category elements. Examples: + * - Single element: "{Locale}" might generate "neon-lit city." + * - Multiple elements: "{Adjective:2}" can give "rainy, bustling." + * - Random range: "{Object:1-3}" could return "hovercar" or "hovercar, android, neon sign." + * + * BatchCount: + * - BatchCount sets the number of prompts to generate. + * - Change its value with the slider to control output (e.g., `BatchCount = 15;` for fifteen prompts). + * + * Customize your script to create diverse and inspiring prompts for "Draw Things." + * + * User Interface: + * - Use UI Prompt: + * Process the prompt in the UI box instead of selecting random prompts. + * - Lock configuration: + * When selecting random prompts, do not change configurations. + * - Iterate Mode: + * Iterated generation of all combinations of dynamic prompt. Best used with UI Prompt. + * BatchCount is not used. Iterate mode can create very large numbers. + * To reduce the numbers, eliminate a few dynamic categories from your prompt. + */ + +//Version +const versionString = "3.5" +//Maximum iterations for Iterate Mode +const maxIter = 500 +//store selected prompt/LoRA data +let promptData; +let userPrompt = ''; +// Default example prompt for UI demonstrating category use +const defaultPrompt = "Cybervenues wide-angle shot of {weather} {time} {locale}" + +/* These are the prompts randomly selected from if UI Prompt isn't valid. + Modify prompts to provide dynamic prompts with LoRAs which will be randomly selected from if a valid dynamic prompt is not found in the UI. + As of 3.0.1 there can be a 'configuration' object for each prompt which contains arbitrary settings which will be passed on to the pipeline at runtime. These settings correspond to the possible values of pipeline.configuration + There is an important caveat: Sampler is tricky because it looks like a string with the sampler name but it's actually an integer from a lookup table, so you want to find the number that corresponds to the desired sampler and use that instead (without quotes). + + Here is the current sampler lookup table: + SamplerType = { + DPMPP_2M_KARRAS: 0, + EULER_A: 1, + DDIM: 2, + PLMS: 3, + DPMPP_SDE_KARRAS: 4, + UNI_PC: 5, + LCM: 6, + EULER_A_SUBSTEP: 7, + DPMPP_SDE_SUBSTEP: 8, + TCD: 9, + EULER_A_TRAILING: 10, + DPMPP_SDE_TRAILING: 11, + DPMPP_2M_AYS: 12, + EULER_A_AYS: 13, + DPMPP_SDE_AYS: 14, + DPMPP_2M_TRAILING: 15, + DDIM_TRAILING: 16 +} + */ + +const prompts = [ + { + prompt: "{colors:1-3} dominant {camera} shot of a {adjective} cyborg woman, {style}, {hairstyle} {haircolor} hair, {features}, {pose} in {weather} {time} {locale}", + negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", + model: "FLUX.1 [schnell] (8-bit)", + loras: [ + { file: "AntiBlur v1.0 [dev]", weight: 0.4} + ], + configuration: + {width:1152,height:896,steps:2,sampler:10,guidanceScale:2.5,clipLText:"Photograph, sensual, professional",resolutionDependentShift:true} + }, + { + prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", + negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", + model: "RealVisXL v4.0", + loras: [ + { file: "Fix Hands Slider", weight: 0.3 }, + { file: "Pixel Art XL v1.1", weight: 0.4 } + ], + configuration: + {width:1024,height:1024,steps:28,sampler:0,guidanceScale:4.0} + }, + { + prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", + negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", + model: "RealVisXL v4.0", + loras: [ + { file: "Fix Hands Slider", weight: 0.3 }, + { file: "Pixel Art XL v1.1", weight: 0.4 } + ], + configuration: + {width:1024,height:1024,steps:28,sampler:0,guidanceScale:4.0} + } + + // Add more prompt objects as needed + // Empty file will be ignored +]; + +// Categories definition +const categories = { + time: [ + "morning", + "noon", + "night", + "midnight", + "sunset", + "sunrise" + ], + camera: [ + "full body", + "medium", + "close_up", + "establishing" + ], + locale: [ + "cityscape", + "street", + "alley", + "marketplace", + "industrial zone", + "waterfront", + "forest", + "rooftop", + "bridge", + "park" + ], + weather: [ + "rainy", + "smoggy", + "stormy", + "dusty" + ], + adjective: [ + "beautiful", + "moody", + "cute", + "tired", + "injured", + "cyborg", + "muscular" + ], + style: [ + "cyberpunk street", "dark gothic", "military armor", "elegant kimono", "eveningwear dress", "corpo uniform", "tech bodysuit" + ], + hairstyle: [ + "long", + "medium", + "shaved", + "short", + "wet", + "punk" + ], + haircolor: [ + "red", + "blonde", + "colored", + "brunette", + "natural", + "streaked" + ], + colors: [ + "red", + "blue", + "green", + "yellow", + "orange", + "purple", + "pink", + "brown", + "black", + "white", + "gray", + "cyan", + "magenta", + "teal", + "olive", + "maroon", + "navy", + "lime", + "indigo", + "gold", + "silver", + "bronze", + "salmon", + "coral", + "turquoise", + "peach", + "lavender", + "emerald", + "ruby", + "sapphire" + ], + features: [ + "{colors} glowing cyborg eyes", + "smoking", + "tech brella with {colors} glowing accents", + "cyborg arm", + "cyborg legs", + "optics implant", + "tech goggles" + ], + pose: [ + "action pose", + "poses", + "model pose", + "relaxing", + "sitting" + ], + malestyle: [ + "leather", + "denim", + "silk", + ] +}; + +// UI +const categoryNames = Object.keys(categories).join(', '); +const header = "Dynamic Prompts " + `${versionString}` + " by zanshinmu"; +const aboutText = "Selects randomly from " + `${prompts.length}`+ " dynamic prompts" + "\nGenerates batch images using '{}' to randomize categories in prompt" +const userSelection = requestFromUser("Dynamic Prompts", "Start", function() { + return [ + this.section(header, aboutText, []), + this.section("Categories", categoryNames, [ + this.textField(defaultPrompt, pipeline.prompts.prompt, true, 60), + this.slider(10, this.slider.fractional(0), 1, 2000, "batch count"), + this.switch(false, "Use UI Prompt"), + this.switch(false, "Lock configuration"), + this.switch(false, "Iterate Mode"), + this.switch(true, "Download Models") + ]) + ]; +}); +// Number of prompts to generate +let batchCount = userSelection[1][1]; +let uiPrompt = userSelection[1][0]; +let useUiPrompt = userSelection[1][2]; +let overrideModels = userSelection[1][3]; +let iterateMode = userSelection[1][4]; +let downloadModels = userSelection[1][5]; +if (iterateMode){ + console.log("Iterate Mode"); +} +//console.log(JSON.stringify(userSelection)); + +// Get configuration +const configuration = pipeline.configuration; +const defaultLoras = pipeline.configuration.loras; +//console.log(JSON.stringify(configuration)); +const uiNegPrompt = pipeline.prompts.negativePrompt; +if (isPromptValid(uiPrompt, categories)) { + if(useUiPrompt){ + console.log("Valid UI prompt detected."); + userPrompt = uiPrompt; // Use the UI prompt + } +} + +// Main batch loop +if (!iterateMode){ + for (let i = 0; i < batchCount; i++){ + render(getPrompt()); + let batchCountLog = `batch ${i + 1} of ${batchCount}\n`; + console.log(batchCountLog); + } +} else { + let dynprompt; + if (useUiPrompt){ + dynPrompt = userPrompt; + } else { + dynPrompt = getPromptString(selectRandomPrompt()); + } + p = computeTotalPromptCount(dynPrompt); + if (p > maxIter){ + console.log(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); + console.log("Reduce the number of categories used in prompt.") + return; + } + let k = 1; + console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); + for (let generatedPrompt of generatePrompts(dynPrompt)) { + console.log(`iterating render ${k} of ${p}\n`); + render(generatedPrompt); // Do something with each generated prompt + k++; + } +} + +function computeTotalPromptCount(dynamicPrompt) { + let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); + let totalCombinationCount = 1; + for (let placeholder of placeholders) { + const valueCount = categories[placeholder].length; // Get the number of values in each category + totalCombinationCount *= valueCount; // Multiply the counts to calculate the maximum possible number of combinations + } + return totalCombinationCount; +} + +function* generatePrompts(dynamicPrompt) { + function cartesian(...arrays) { + return arrays.reduce((acc, curr) => acc.flatMap(d => curr.map(e => [d, e].flat()))); + } + + let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); + let validPlaceholders = placeholders.filter(p => categories[p]); + let categoryValues = validPlaceholders.map(p => categories[p]); + let combinations = cartesian(...categoryValues); + + for (let combination of combinations) { + let prompt = dynamicPrompt; + validPlaceholders.forEach((placeholder, i) => { + prompt = prompt.replace(`{${placeholder}}`, combination[i]); + }); + yield prompt; + } +} + + +function selectRandomPrompt() { + // Generate a random index to select a random prompt + const randomIndex = Math.floor(Math.random() * prompts.length); + console.log(`Selected ${randomIndex} of ${prompts.length}`) + // Get the randomly selected prompt object + const selectedPrompt = prompts[randomIndex]; + // Extract prompt string, LoRa filenames, and weights + let myprompt = selectedPrompt.prompt; + let myneg = selectedPrompt.negativePrompt; + let mymodel = selectedPrompt.model; + let myconfig = selectedPrompt.configuration; + const loras = selectedPrompt.loras.map(lora => ({ file: lora.file, weight: lora.weight })); + let myloras = getAssociatedLoRas(loras); + myconfig.model = mymodel; + myconfig.loras=myloras; + // Store the promptData object + let promptData = { prompt: myprompt, negativePrompt: myneg, configuration: myconfig }; + if (downloadModels){ + getModels(promptData); + } + return promptData; +} + +//Download models if necessary +function getModels(promptData){ + let models = []; + //Model first + models.push(promptData.configuration.model); + //Initiate Download + pipeline.downloadBuiltins(models); + + //Loras + for (let i = 0; i < promptData.configuration.loras.length; i++) { + let lora = promptData.configuration.loras[i].file; + models.push(lora); + } + //Initiate Download + pipeline.downloadBuiltins(models); +} + +function loraNamestoFiles(loras){ + for (let i = 0; i < loras.length; i++) { + let loraname = loras[i].file; + let lorafile = pipeline.findLoRAByName(loraname); + loras[i]=lorafile; + } + return loras; +} + +// Function to get the prompt string from the object returned by selectRandomPrompt +function getPromptString(p) { + return p.prompt; +} + +// Function to extract and validate category names and their requested item count or range from the uiPrompt +function isPromptValid(uiPrompt, categories) { + const regex = /{(\w+)(?::(\d+)(?:-(\d+))?)?}/g; // Extended regex to capture syntax variations + let isValid = false; // Assume the prompt is invalid initially + + let match; + while ((match = regex.exec(uiPrompt)) !== null) { + const [fullMatch, categoryName, itemCount, rangeEnd] = match; + + // Check if the category name is valid + if (!(categoryName in categories)) { + console.log(`Invalid UI prompt: Category '${categoryName}' not found.`); + return false; // Invalid if the category doesn't exist + } + + // Validate item count or range syntax if specified + if (itemCount) { + isValid = true; // Assume valid syntax if itemCount exists + if (rangeEnd && (parseInt(itemCount) > parseInt(rangeEnd))) { + console.log(`Invalid UI prompt: Incorrect range '${itemCount}-${rangeEnd}' in category '${categoryName}'.`); + return false; // Invalid if the range start is greater than the range end + } + } else { + // Valid if only the category name is specified without itemCount or range + isValid = true; + } + } + + // Prompt is invalid if no categories or incorrect syntax is detected + if (!isValid) { + console.log("Invalid UI prompt: No valid categories or incorrect syntax detected."); + } + return isValid; +} + +//get prompt each iteration +function getPrompt () { + if (useUiPrompt) { + console.log("Using UI Prompt"); + promptString = userPrompt; + } else { + console.log("Selecting dynamic prompt and configuration."); + promptData = selectRandomPrompt(); + //console.log(promptData); + promptString = getPromptString(promptData); + } + return promptString +} + +function getAssociatedLoRas(loras) { + if(!overrideModels){ + let validLoras = setLoras(loras); + const mergedLoras = defaultLoras.concat(validLoras); + return mergedLoras + }else{ + return defaultLoras + } +} + +// is name in defaultLoras? +function isDefaultLora(lora){ + for (let i = 0; i < defaultLoras.length; i++) { + if (defaultLoras[i].file === lora.file){ + return true; + } else{ + return false; + } + } +} + + +// Only overwrite valid Loras from prompts +function setLoras (myLoras){ + let loras=[]; + for (let i = 0; i < myLoras.length; i++) { + if (myLoras[i].file !== ''){ + if(!isDefaultLora(myLoras[i])){ + loras.push(myLoras[i]); + } + } + } + return loras +} + +// Run pipeline +function render (promptString){ + let editedString = replaceWildcards(promptString, categories); + let neg; + let myConfiguration = configuration; + if (useUiPrompt){ + neg = uiNegPrompt; + } else { + neg = promptData.negativePrompt; + if (!overrideModels){ + myConfiguration.model = promptData.model; + //Default to random seed, configuration overrides + myConfiguration.seed = -1; + //Apply configuration changes, if any + myConfiguration = Object.assign(configuration, promptData.configuration); + myConfiguration.loras = loraNamestoFiles(promptData.configuration.loras); + } + } + //Clear canvas + canvas.clear(); + pipeline.run({ + configuration: myConfiguration, + prompt: editedString, + negativePrompt: neg + }); +} + +// Function to replace wildcards with random options +function replaceWildcards(promptString, categories) { + const wildcardRegex = /{(\w+)(?::(\d+)(?:-(\d+))?)?}/g; + + function replaceWildcard(match, categoryName, minCount, maxCount) { + const categoryOptions = categories[categoryName]; + if (categoryOptions) { + const count = getRandomCount(minCount, maxCount); + const options = new Set(); // Use a Set to ensure uniqueness + + while (options.size < count) { + let randomOption = categoryOptions[Math.floor(Math.random() * categoryOptions.length)]; + + // Check if the selected option contains another wildcard + if (wildcardRegex.test(randomOption)) { + // Recursively expand the wildcard in the selected option + randomOption = replaceWildcards(randomOption, categories); + } + + options.add(randomOption); + } + + return [...options].join(", "); + } + return match; // If category not found, return the original match + } + + // Recursively replace all wildcards in the prompt string + let editedString = promptString.replace(wildcardRegex, replaceWildcard); + + return editedString; +} + +// Function to get a random count within the specified range or default to 1 if range not provided +function getRandomCount(minCount, maxCount) { + const min = parseInt(minCount); + const max = parseInt(maxCount); + + if (!isNaN(min) && !isNaN(max)) { + // Both min and max are numbers, return a random number in this range + return Math.floor(Math.random() * (max - min + 1)) + min; + } else if (!isNaN(min)) { + // Only min is a number, return this number + return min; + } else { + // Default case when neither min nor max is a number + return 1; + } +} diff --git a/scripts/dynamic-prompts/metadata.json b/scripts/dynamic-prompts/metadata.json new file mode 100644 index 0000000..363ed3f --- /dev/null +++ b/scripts/dynamic-prompts/metadata.json @@ -0,0 +1,6 @@ +{ + "name": "Dynamic Prompts for Draw Things", + "author": "Zanshinmu", + "file": "dynamic_prompts.js", + "description": "Allows dynamic prompts features similar to sd-dynamic-prompts in Draw Things." +} From dcc00db7828d0c32a0c431ee458e1cae8b8fddaf Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Mon, 14 Oct 2024 13:32:27 -0700 Subject: [PATCH 02/13] Added render timer, bugfix --- scripts/dynamic-prompts/dynamic_prompts.js | 43 ++++++++++++++-------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic_prompts.js b/scripts/dynamic-prompts/dynamic_prompts.js index 71f72f6..2cb927c 100644 --- a/scripts/dynamic-prompts/dynamic_prompts.js +++ b/scripts/dynamic-prompts/dynamic_prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author zanshinmu -// v3.5 +// v3.5.1 /** * Documentation for "Dynamic Prompts" Script (Version 3.5) for "Draw Things" * @@ -37,14 +37,14 @@ */ //Version -const versionString = "3.5" +const versionString = "3.5.1" //Maximum iterations for Iterate Mode const maxIter = 500 //store selected prompt/LoRA data let promptData; let userPrompt = ''; // Default example prompt for UI demonstrating category use -const defaultPrompt = "Cybervenues wide-angle shot of {weather} {time} {locale}" +const defaultPrompt = "wide-angle shot of {weather} {time} {locale}" /* These are the prompts randomly selected from if UI Prompt isn't valid. Modify prompts to provide dynamic prompts with LoRAs which will be randomly selected from if a valid dynamic prompt is not found in the UI. @@ -272,9 +272,9 @@ if (isPromptValid(uiPrompt, categories)) { // Main batch loop if (!iterateMode){ for (let i = 0; i < batchCount; i++){ - render(getPrompt()); - let batchCountLog = `batch ${i + 1} of ${batchCount}\n`; + let batchCountLog = `Rendering batch ${i + 1} of ${batchCount}`; console.log(batchCountLog); + render(getPrompt()); } } else { let dynprompt; @@ -286,15 +286,15 @@ if (!iterateMode){ p = computeTotalPromptCount(dynPrompt); if (p > maxIter){ console.log(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); - console.log("Reduce the number of categories used in prompt.") - return; - } - let k = 1; - console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); - for (let generatedPrompt of generatePrompts(dynPrompt)) { - console.log(`iterating render ${k} of ${p}\n`); - render(generatedPrompt); // Do something with each generated prompt - k++; + console.log("Reduce the number of categories used in prompt."); + } else { + let k = 1; + console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); + for (let generatedPrompt of generatePrompts(dynPrompt)) { + console.log(`iterating render ${k} of ${p}\n`); + render(generatedPrompt); // Do something with each generated prompt + k++; + } } } @@ -331,7 +331,7 @@ function* generatePrompts(dynamicPrompt) { function selectRandomPrompt() { // Generate a random index to select a random prompt const randomIndex = Math.floor(Math.random() * prompts.length); - console.log(`Selected ${randomIndex} of ${prompts.length}`) + console.log(`Selected prompt/configuration ${randomIndex} of ${prompts.length}`) // Get the randomly selected prompt object const selectedPrompt = prompts[randomIndex]; // Extract prompt string, LoRa filenames, and weights @@ -423,7 +423,6 @@ function getPrompt () { console.log("Using UI Prompt"); promptString = userPrompt; } else { - console.log("Selecting dynamic prompt and configuration."); promptData = selectRandomPrompt(); //console.log(promptData); promptString = getPromptString(promptData); @@ -466,8 +465,18 @@ function setLoras (myLoras){ return loras } +function timer (start){ + const end = Date.now(); + const duration = end - start; + const minutes = Math.floor(duration / 60000); + let seconds = Math.floor((duration % 60000) / 1000); + seconds = seconds < 10 ? '0' + seconds : seconds; + console.log(`✔︎ Render time ‣ ${minutes}:${seconds}\n`); +} + // Run pipeline function render (promptString){ + let start = Date.now(); let editedString = replaceWildcards(promptString, categories); let neg; let myConfiguration = configuration; @@ -491,6 +500,8 @@ function render (promptString){ prompt: editedString, negativePrompt: neg }); + //Output render time elapsed + timer(start); } // Function to replace wildcards with random options From 0c79b42d0206adef8c0d148b5afb0537fda60b26 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Mon, 14 Oct 2024 17:03:11 -0700 Subject: [PATCH 03/13] Fixed wildcard expansion bug with deeply nested placeholders --- scripts/dynamic-prompts/dynamic_prompts.js | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic_prompts.js b/scripts/dynamic-prompts/dynamic_prompts.js index 2cb927c..cb01a64 100644 --- a/scripts/dynamic-prompts/dynamic_prompts.js +++ b/scripts/dynamic-prompts/dynamic_prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author zanshinmu -// v3.5.1 +// v3.5.2 /** * Documentation for "Dynamic Prompts" Script (Version 3.5) for "Draw Things" * @@ -37,7 +37,7 @@ */ //Version -const versionString = "3.5.1" +const versionString = "3.5.2" //Maximum iterations for Iterate Mode const maxIter = 500 //store selected prompt/LoRA data @@ -504,7 +504,6 @@ function render (promptString){ timer(start); } -// Function to replace wildcards with random options function replaceWildcards(promptString, categories) { const wildcardRegex = /{(\w+)(?::(\d+)(?:-(\d+))?)?}/g; @@ -516,13 +515,10 @@ function replaceWildcards(promptString, categories) { while (options.size < count) { let randomOption = categoryOptions[Math.floor(Math.random() * categoryOptions.length)]; - - // Check if the selected option contains another wildcard - if (wildcardRegex.test(randomOption)) { - // Recursively expand the wildcard in the selected option - randomOption = replaceWildcards(randomOption, categories); - } - + + // Recursively expand the wildcard in the selected option + randomOption = replaceWildcards(randomOption, categories); + options.add(randomOption); } @@ -531,12 +527,17 @@ function replaceWildcards(promptString, categories) { return match; // If category not found, return the original match } - // Recursively replace all wildcards in the prompt string - let editedString = promptString.replace(wildcardRegex, replaceWildcard); + let editedString = promptString; + + // Recursively replace wildcards until there are none left + while (wildcardRegex.test(editedString)) { + editedString = editedString.replace(wildcardRegex, replaceWildcard); + } return editedString; } + // Function to get a random count within the specified range or default to 1 if range not provided function getRandomCount(minCount, maxCount) { const min = parseInt(minCount); From bdf336a3e62d2ef9a70e9cc629c26b20123f48bc Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 18 Oct 2024 02:11:28 -0700 Subject: [PATCH 04/13] Fixed iterator bug, added output location, updated text, separated UI Prompt from main UI --- scripts/dynamic-prompts/dynamic_prompts.js | 107 +++++++++++++++------ 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic_prompts.js b/scripts/dynamic-prompts/dynamic_prompts.js index cb01a64..5f8aa34 100644 --- a/scripts/dynamic-prompts/dynamic_prompts.js +++ b/scripts/dynamic-prompts/dynamic_prompts.js @@ -1,9 +1,11 @@ //@api-1.0 // dynamic prompts -// author zanshinmu -// v3.5.2 +// author: zanshinmu +// v3.5.4 +// Discord Thread for Dynamic Prompts: +// https://discord.com/channels/1038516303666876436/1207467278426177736 /** - * Documentation for "Dynamic Prompts" Script (Version 3.5) for "Draw Things" + * Documentation for "Dynamic Prompts" Script (Version 3.5.4) for "Draw Things" * * This script generates dynamic prompts for the "Draw Things" application. Customize it to enhance your creative experience. * @@ -28,27 +30,32 @@ * User Interface: * - Use UI Prompt: * Process the prompt in the UI box instead of selecting random prompts. + * - Lock configuration: * When selecting random prompts, do not change configurations. + * - Iterate Mode: * Iterated generation of all combinations of dynamic prompt. Best used with UI Prompt. - * BatchCount is not used. Iterate mode can create very large numbers. - * To reduce the numbers, eliminate a few dynamic categories from your prompt. + * BatchCount is not used in Iterate mode. Iterate mode can create very large numbers. + * To reduce the numbers, eliminate categories from your prompt. */ //Version -const versionString = "3.5.2" +const versionString = "v3.5.4" //Maximum iterations for Iterate Mode const maxIter = 500 //store selected prompt/LoRA data let promptData; let userPrompt = ''; +let uiPrompt = ''; // Default example prompt for UI demonstrating category use const defaultPrompt = "wide-angle shot of {weather} {time} {locale}" /* These are the prompts randomly selected from if UI Prompt isn't valid. Modify prompts to provide dynamic prompts with LoRAs which will be randomly selected from if a valid dynamic prompt is not found in the UI. + As of 3.0.1 there can be a 'configuration' object for each prompt which contains arbitrary settings which will be passed on to the pipeline at runtime. These settings correspond to the possible values of pipeline.configuration + There is an important caveat: Sampler is tricky because it looks like a string with the sampler name but it's actually an integer from a lookup table, so you want to find the number that corresponds to the desired sampler and use that instead (without quotes). Here is the current sampler lookup table: @@ -230,51 +237,77 @@ const categories = { // UI const categoryNames = Object.keys(categories).join(', '); +const okButton = "Start"; const header = "Dynamic Prompts " + `${versionString}` + " by zanshinmu"; -const aboutText = "Selects randomly from " + `${prompts.length}`+ " dynamic prompts" + "\nGenerates batch images using '{}' to randomize categories in prompt" -const userSelection = requestFromUser("Dynamic Prompts", "Start", function() { +const aboutText = "Selects randomly from " + `${prompts.length} dynamic prompts`+ + " located in the script's 'const prompts' object."; +const userSelection = requestFromUser("Dynamic Prompts", okButton, function() { return [ - this.section(header, aboutText, []), - this.section("Categories", categoryNames, [ - this.textField(defaultPrompt, pipeline.prompts.prompt, true, 60), - this.slider(10, this.slider.fractional(0), 1, 2000, "batch count"), - this.switch(false, "Use UI Prompt"), + this.section(header, aboutText, [ + this.switch(false, "Enter UI Prompt"), this.switch(false, "Lock configuration"), this.switch(false, "Iterate Mode"), - this.switch(true, "Download Models") + this.switch(true, "Download Models"), + this.slider(10, this.slider.fractional(0), 1, 2000, "batch count"), + ]), + this.section("Output:", "Output rendered images to custom location", [ + this.directory('${filesystem.pictures.path}') ]) ]; }); -// Number of prompts to generate -let batchCount = userSelection[1][1]; -let uiPrompt = userSelection[1][0]; -let useUiPrompt = userSelection[1][2]; -let overrideModels = userSelection[1][3]; -let iterateMode = userSelection[1][4]; -let downloadModels = userSelection[1][5]; +// Parse UI input +let useUiPrompt = userSelection[0][0]; +let overrideModels = userSelection[0][1]; +let iterateMode = userSelection[0][2]; +let downloadModels = userSelection[0][3]; +let batchCount = userSelection[0][4]; +let outputDir = userSelection[1][0]; + if (iterateMode){ console.log("Iterate Mode"); } + +if (useUiPrompt) { + const userSelection = requestFromUser("Dynamic Prompts: UI Prompt", okButton, function() { + return [ + this.section("Dynamic Prompt Syntax","", [ + this.section("{category}","Replaces category with random item", []), + this.section("{category:2}","Replaces category with 2 random items", []), + this.section("{category:1-3}","Replaces category with 1 to 3 random items", []), + this.section("UI Prompt", "Modify the dynamic prompt to your desire", [ + this.textField(defaultPrompt, pipeline.prompts.prompt, true, 80), + this.section("Available Categories", categoryNames, []) + ]) + ]) + ]; // Closing bracket for the return array + }); + uiPrompt = userSelection[0][3][0]; +} //console.log(JSON.stringify(userSelection)); // Get configuration const configuration = pipeline.configuration; const defaultLoras = pipeline.configuration.loras; -//console.log(JSON.stringify(configuration)); const uiNegPrompt = pipeline.prompts.negativePrompt; -if (isPromptValid(uiPrompt, categories)) { - if(useUiPrompt){ + +if(useUiPrompt){ + if(isPromptValid(uiPrompt, categories)){ console.log("Valid UI prompt detected."); userPrompt = uiPrompt; // Use the UI prompt + } else { + console.log("Error, Invalid UI Prompt"); + return; } } // Main batch loop if (!iterateMode){ for (let i = 0; i < batchCount; i++){ - let batchCountLog = `Rendering batch ${i + 1} of ${batchCount}`; + let batchCountLog = `Rendering ${i + 1} of ${batchCount}`; console.log(batchCountLog); render(getPrompt()); + //Save to custom location + customImageSave(pipeline, i); } } else { let dynprompt; @@ -291,13 +324,25 @@ if (!iterateMode){ let k = 1; console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); for (let generatedPrompt of generatePrompts(dynPrompt)) { - console.log(`iterating render ${k} of ${p}\n`); - render(generatedPrompt); // Do something with each generated prompt + console.log(`iterating render ${k} of ${p}\n${generatedPrompt}\n`); + render(generatedPrompt); + //Save to custom location + customImageSave(pipeline, k); k++; } } } +function customImageSave(pipeline, batchCount){ + if(outputDir) { + // Save File + let saveLocation = `${outputDir}/${pipeline.configuration.seed}_${Date.now()}_${batchCount}.png` + console.log(`Saving to ${saveLocation}\n\n`); + canvas.saveImage(saveLocation, true); // save the image currently on canvas to a file. + return saveLocation; + } +} + function computeTotalPromptCount(dynamicPrompt) { let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); let totalCombinationCount = 1; @@ -310,7 +355,10 @@ function computeTotalPromptCount(dynamicPrompt) { function* generatePrompts(dynamicPrompt) { function cartesian(...arrays) { - return arrays.reduce((acc, curr) => acc.flatMap(d => curr.map(e => [d, e].flat()))); + if (arrays.length === 0) return []; + return arrays.reduce((acc, curr) => { + return acc.flatMap(a => curr.map(b => [].concat(a, b))); + }, [[]]); } let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); @@ -327,11 +375,10 @@ function* generatePrompts(dynamicPrompt) { } } - function selectRandomPrompt() { // Generate a random index to select a random prompt const randomIndex = Math.floor(Math.random() * prompts.length); - console.log(`Selected prompt/configuration ${randomIndex} of ${prompts.length}`) + console.log(`Selected dynamic prompt ${randomIndex} of ${prompts.length}`) // Get the randomly selected prompt object const selectedPrompt = prompts[randomIndex]; // Extract prompt string, LoRa filenames, and weights From 153fd18dde8884ba8e08af51c6536775c69fb122 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 18 Oct 2024 02:35:38 -0700 Subject: [PATCH 05/13] Resolving conflicts with main repo --- .../{dynamic_prompts.js => dynamic-prompts.js} | 0 scripts/dynamic-prompts/metadata.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/dynamic-prompts/{dynamic_prompts.js => dynamic-prompts.js} (100%) diff --git a/scripts/dynamic-prompts/dynamic_prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js similarity index 100% rename from scripts/dynamic-prompts/dynamic_prompts.js rename to scripts/dynamic-prompts/dynamic-prompts.js diff --git a/scripts/dynamic-prompts/metadata.json b/scripts/dynamic-prompts/metadata.json index 363ed3f..5f93067 100644 --- a/scripts/dynamic-prompts/metadata.json +++ b/scripts/dynamic-prompts/metadata.json @@ -1,6 +1,6 @@ { - "name": "Dynamic Prompts for Draw Things", + "name": "Dynamic Prompts", "author": "Zanshinmu", - "file": "dynamic_prompts.js", + "file": "dynamic-prompts.js", "description": "Allows dynamic prompts features similar to sd-dynamic-prompts in Draw Things." } From e8a7afd10684947347dda935e342a75e2b74b2b3 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 18 Oct 2024 09:44:57 -0700 Subject: [PATCH 06/13] Fix to remove -1 from filename, disable batchSize > 1 --- scripts/dynamic-prompts/dynamic-prompts.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index 5f8aa34..f6c3c6e 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author: zanshinmu -// v3.5.4 +// v3.5.5 // Discord Thread for Dynamic Prompts: // https://discord.com/channels/1038516303666876436/1207467278426177736 /** @@ -41,7 +41,7 @@ */ //Version -const versionString = "v3.5.4" +const versionString = "v3.5.5" //Maximum iterations for Iterate Mode const maxIter = 500 //store selected prompt/LoRA data @@ -527,14 +527,13 @@ function render (promptString){ let editedString = replaceWildcards(promptString, categories); let neg; let myConfiguration = configuration; + myConfiguration.batchSize = 1; if (useUiPrompt){ neg = uiNegPrompt; } else { neg = promptData.negativePrompt; if (!overrideModels){ myConfiguration.model = promptData.model; - //Default to random seed, configuration overrides - myConfiguration.seed = -1; //Apply configuration changes, if any myConfiguration = Object.assign(configuration, promptData.configuration); myConfiguration.loras = loraNamestoFiles(promptData.configuration.loras); From 2202615c78fce9f8f0c9cc7058d9a0b9fe04a62a Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 18 Oct 2024 15:32:18 -0700 Subject: [PATCH 07/13] Added image seed control, defaults to increment --- scripts/dynamic-prompts/dynamic-prompts.js | 66 ++++++++++++++++------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index f6c3c6e..a6a582c 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author: zanshinmu -// v3.5.5 +// v3.5.6 // Discord Thread for Dynamic Prompts: // https://discord.com/channels/1038516303666876436/1207467278426177736 /** @@ -41,7 +41,7 @@ */ //Version -const versionString = "v3.5.5" +const versionString = "v3.5.6" //Maximum iterations for Iterate Mode const maxIter = 500 //store selected prompt/LoRA data @@ -237,31 +237,36 @@ const categories = { // UI const categoryNames = Object.keys(categories).join(', '); +const seedOptions = ["Random","Increment","Static"]; const okButton = "Start"; const header = "Dynamic Prompts " + `${versionString}` + " by zanshinmu"; const aboutText = "Selects randomly from " + `${prompts.length} dynamic prompts`+ " located in the script's 'const prompts' object."; + const userSelection = requestFromUser("Dynamic Prompts", okButton, function() { return [ this.section(header, aboutText, [ - this.switch(false, "Enter UI Prompt"), - this.switch(false, "Lock configuration"), - this.switch(false, "Iterate Mode"), - this.switch(true, "Download Models"), - this.slider(10, this.slider.fractional(0), 1, 2000, "batch count"), - ]), - this.section("Output:", "Output rendered images to custom location", [ - this.directory('${filesystem.pictures.path}') + this.section("Seed Mode", "", [this.segmented(1, seedOptions), + this.slider(10, this.slider.fractional(0), 1, 2000, "batch count")]), + this.section("Output:", "Output rendered images to custom location", [ + this.directory(`${filesystem.pictures.path}`)]), + this.switch(false, "Enter UI Prompt"), + this.switch(false, "Lock configuration"), + this.switch(false, "Iterate Mode"), + this.switch(true, "Download Models"), ]) ]; }); + // Parse UI input -let useUiPrompt = userSelection[0][0]; -let overrideModels = userSelection[0][1]; -let iterateMode = userSelection[0][2]; -let downloadModels = userSelection[0][3]; -let batchCount = userSelection[0][4]; -let outputDir = userSelection[1][0]; +const seedMode = userSelection[0][0][0]; +const batchCount = userSelection[0][0][1]; +const outputDir = userSelection[0][1][0]; +const useUiPrompt = userSelection[0][2]; +const overrideModels = userSelection[0][3]; +const iterateMode = userSelection[0][4]; +const downloadModels = userSelection[0][5]; + if (iterateMode){ console.log("Iterate Mode"); @@ -283,7 +288,7 @@ if (useUiPrompt) { }); uiPrompt = userSelection[0][3][0]; } -//console.log(JSON.stringify(userSelection)); + // Get configuration const configuration = pipeline.configuration; @@ -527,7 +532,11 @@ function render (promptString){ let editedString = replaceWildcards(promptString, categories); let neg; let myConfiguration = configuration; + let mySeed = configuration.seed; myConfiguration.batchSize = 1; + // Set seed + myConfiguration.seed = getSeed(mySeed); + if (useUiPrompt){ neg = uiNegPrompt; } else { @@ -550,6 +559,29 @@ function render (promptString){ timer(start); } +function getSeed(oldSeed){ + const MAX_INT_32 = 2147483647; + let seed = 0; + + switch (seedMode) { + case 0: // Random + seed = Math.floor(Math.random() * (MAX_INT_32)); + break; + + case 1: // Iterate + if (seed < MAX_INT_32){ //Wrap if seed exceeds MAX_INT_32 + seed = ++oldSeed; + } + break; + + case 2: // Static + seed = oldSeed; + break; + } + console.log(`${seedOptions[seedMode]} Seed Mode, Seed: ${seed}`); + return seed; +} + function replaceWildcards(promptString, categories) { const wildcardRegex = /{(\w+)(?::(\d+)(?:-(\d+))?)?}/g; From d8bb74d62fe176eac8349bce3dd9fb9288251ee1 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Tue, 22 Oct 2024 14:33:09 -0700 Subject: [PATCH 08/13] Bugfix for prompts loras not setting weights and occasional DT crash. Now overrides UI configuration loras unless 'lock config' is enabled. --- scripts/dynamic-prompts/dynamic-prompts.js | 73 +++++++--------------- 1 file changed, 23 insertions(+), 50 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index a6a582c..91a281e 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -41,13 +41,15 @@ */ //Version -const versionString = "v3.5.6" +const versionString = "v3.5.7"; //Maximum iterations for Iterate Mode -const maxIter = 500 +const maxIter = 500; +const DEBUG = false; //store selected prompt/LoRA data let promptData; let userPrompt = ''; let uiPrompt = ''; + // Default example prompt for UI demonstrating category use const defaultPrompt = "wide-angle shot of {weather} {time} {locale}" @@ -292,7 +294,6 @@ if (useUiPrompt) { // Get configuration const configuration = pipeline.configuration; -const defaultLoras = pipeline.configuration.loras; const uiNegPrompt = pipeline.prompts.negativePrompt; if(useUiPrompt){ @@ -309,7 +310,7 @@ if(useUiPrompt){ if (!iterateMode){ for (let i = 0; i < batchCount; i++){ let batchCountLog = `Rendering ${i + 1} of ${batchCount}`; - console.log(batchCountLog); + console.warn(batchCountLog); render(getPrompt()); //Save to custom location customImageSave(pipeline, i); @@ -323,13 +324,13 @@ if (!iterateMode){ } p = computeTotalPromptCount(dynPrompt); if (p > maxIter){ - console.log(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); - console.log("Reduce the number of categories used in prompt."); + console.warn(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); + console.warn("Reduce the number of categories used in prompt."); } else { let k = 1; console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); for (let generatedPrompt of generatePrompts(dynPrompt)) { - console.log(`iterating render ${k} of ${p}\n${generatedPrompt}\n`); + console.warn(`iterating render ${k} of ${p}\n${generatedPrompt}\n`); render(generatedPrompt); //Save to custom location customImageSave(pipeline, k); @@ -391,10 +392,11 @@ function selectRandomPrompt() { let myneg = selectedPrompt.negativePrompt; let mymodel = selectedPrompt.model; let myconfig = selectedPrompt.configuration; - const loras = selectedPrompt.loras.map(lora => ({ file: lora.file, weight: lora.weight })); - let myloras = getAssociatedLoRas(loras); + //Prepare LoRAs + const myLoras = resolveLoras(selectedPrompt.loras); + const loras = myLoras.map(lora => ({ file: lora.file, weight: lora.weight })); myconfig.model = mymodel; - myconfig.loras=myloras; + myconfig.loras = loras; // Store the promptData object let promptData = { prompt: myprompt, negativePrompt: myneg, configuration: myconfig }; if (downloadModels){ @@ -420,11 +422,17 @@ function getModels(promptData){ pipeline.downloadBuiltins(models); } -function loraNamestoFiles(loras){ +// Resolves whether LoRAs are filenames or +function resolveLoras(loras){ + const FILESUFFIX = ".ckpt"; for (let i = 0; i < loras.length; i++) { - let loraname = loras[i].file; - let lorafile = pipeline.findLoRAByName(loraname); - loras[i]=lorafile; + if (!loras[i].file.endsWith(FILESUFFIX)){ + let myfile = pipeline.findLoRAByName(loras[i].file).file; + if (DEBUG){ + console.log(`Filename ${loras[i].file} resolved to ${JSON.stringify(myfile)}`); + } + loras[i].file = myfile; + } } return loras; } @@ -482,41 +490,6 @@ function getPrompt () { return promptString } -function getAssociatedLoRas(loras) { - if(!overrideModels){ - let validLoras = setLoras(loras); - const mergedLoras = defaultLoras.concat(validLoras); - return mergedLoras - }else{ - return defaultLoras - } -} - -// is name in defaultLoras? -function isDefaultLora(lora){ - for (let i = 0; i < defaultLoras.length; i++) { - if (defaultLoras[i].file === lora.file){ - return true; - } else{ - return false; - } - } -} - - -// Only overwrite valid Loras from prompts -function setLoras (myLoras){ - let loras=[]; - for (let i = 0; i < myLoras.length; i++) { - if (myLoras[i].file !== ''){ - if(!isDefaultLora(myLoras[i])){ - loras.push(myLoras[i]); - } - } - } - return loras -} - function timer (start){ const end = Date.now(); const duration = end - start; @@ -545,7 +518,7 @@ function render (promptString){ myConfiguration.model = promptData.model; //Apply configuration changes, if any myConfiguration = Object.assign(configuration, promptData.configuration); - myConfiguration.loras = loraNamestoFiles(promptData.configuration.loras); + myConfiguration.loras = promptData.configuration.loras; } } //Clear canvas From 71b3ab6dd2529c827a96fe9ca5ac772bc0cd634b Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Tue, 22 Oct 2024 21:27:48 -0700 Subject: [PATCH 09/13] Preliminary fix for missing metadata, cleanup of code --- scripts/dynamic-prompts/dynamic-prompts.js | 93 ++++++++++------------ 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index 91a281e..c250d88 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author: zanshinmu -// v3.5.6 +// v3.5.7 // Discord Thread for Dynamic Prompts: // https://discord.com/channels/1038516303666876436/1207467278426177736 /** @@ -45,8 +45,8 @@ const versionString = "v3.5.7"; //Maximum iterations for Iterate Mode const maxIter = 500; const DEBUG = false; -//store selected prompt/LoRA data -let promptData; +//store selected prompt data and UI config +let UICONFIG = pipeline.configuration; let userPrompt = ''; let uiPrompt = ''; @@ -291,9 +291,7 @@ if (useUiPrompt) { uiPrompt = userSelection[0][3][0]; } - -// Get configuration -const configuration = pipeline.configuration; +// Store UI negative prompt for later const uiNegPrompt = pipeline.prompts.negativePrompt; if(useUiPrompt){ @@ -311,18 +309,11 @@ if (!iterateMode){ for (let i = 0; i < batchCount; i++){ let batchCountLog = `Rendering ${i + 1} of ${batchCount}`; console.warn(batchCountLog); - render(getPrompt()); - //Save to custom location - customImageSave(pipeline, i); + render(getPrompt(), i); } } else { - let dynprompt; - if (useUiPrompt){ - dynPrompt = userPrompt; - } else { - dynPrompt = getPromptString(selectRandomPrompt()); - } - p = computeTotalPromptCount(dynPrompt); + let promptData = getPrompt(); + p = computeTotalPromptCount(promptData.prompt); if (p > maxIter){ console.warn(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); console.warn("Reduce the number of categories used in prompt."); @@ -330,25 +321,15 @@ if (!iterateMode){ let k = 1; console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); for (let generatedPrompt of generatePrompts(dynPrompt)) { + let myConfig = promptData; + promptData.prompt = dynPrompt; console.warn(`iterating render ${k} of ${p}\n${generatedPrompt}\n`); - render(generatedPrompt); - //Save to custom location - customImageSave(pipeline, k); + render(generatedPrompt, k); k++; } } } -function customImageSave(pipeline, batchCount){ - if(outputDir) { - // Save File - let saveLocation = `${outputDir}/${pipeline.configuration.seed}_${Date.now()}_${batchCount}.png` - console.log(`Saving to ${saveLocation}\n\n`); - canvas.saveImage(saveLocation, true); // save the image currently on canvas to a file. - return saveLocation; - } -} - function computeTotalPromptCount(dynamicPrompt) { let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); let totalCombinationCount = 1; @@ -402,6 +383,7 @@ function selectRandomPrompt() { if (downloadModels){ getModels(promptData); } + console.warn(JSON.stringify(promptData)); return promptData; } @@ -437,11 +419,6 @@ function resolveLoras(loras){ return loras; } -// Function to get the prompt string from the object returned by selectRandomPrompt -function getPromptString(p) { - return p.prompt; -} - // Function to extract and validate category names and their requested item count or range from the uiPrompt function isPromptValid(uiPrompt, categories) { const regex = /{(\w+)(?::(\d+)(?:-(\d+))?)?}/g; // Extended regex to capture syntax variations @@ -479,15 +456,14 @@ function isPromptValid(uiPrompt, categories) { //get prompt each iteration function getPrompt () { + let promptData = {}; if (useUiPrompt) { console.log("Using UI Prompt"); - promptString = userPrompt; + promptData.prompt = userPrompt; } else { promptData = selectRandomPrompt(); - //console.log(promptData); - promptString = getPromptString(promptData); } - return promptString + return promptData; } function timer (start){ @@ -500,36 +476,51 @@ function timer (start){ } // Run pipeline -function render (promptString){ +function render (promptData, batchCount){ + // start timer let start = Date.now(); - let editedString = replaceWildcards(promptString, categories); + // set generated prompt + let generatedPrompt = replaceWildcards(promptData.prompt, categories); let neg; - let myConfiguration = configuration; - let mySeed = configuration.seed; - myConfiguration.batchSize = 1; - // Set seed - myConfiguration.seed = getSeed(mySeed); + let finalConfiguration = {}; + // Set seed according to user selection + let mySeed = getSeed(pipeline.configuration.seed); + console.log(JSON.stringify(UICONFIG)); if (useUiPrompt){ + finalConfiguration = UICONFIG; + finalConfiguration.loras = UICONFIG.loras; neg = uiNegPrompt; } else { - neg = promptData.negativePrompt; + neg = promptData.negativePrompt; if (!overrideModels){ - myConfiguration.model = promptData.model; //Apply configuration changes, if any - myConfiguration = Object.assign(configuration, promptData.configuration); - myConfiguration.loras = promptData.configuration.loras; + finalConfiguration = Object.assign(UICONFIG, promptData.configuration); + finalConfiguration.loras = promptData.configuration.loras; + console.log(finalConfiguration.model); } } + // Batch > 1 is no bueno + finalConfiguration.batchSize = 1; + finalConfiguration.seed = mySeed; //Clear canvas canvas.clear(); + console.warn(JSON.stringify(finalConfiguration)); pipeline.run({ - configuration: myConfiguration, - prompt: editedString, + configuration: finalConfiguration, + prompt: generatedPrompt, negativePrompt: neg }); //Output render time elapsed timer(start); + //Save Image if enabled + if(outputDir) { + // working around metadata bug by forcing our config onto the UI before saving + pipeline.configuration = finalConfiguration; + let savePath = `${outputDir}/${finalConfiguration.seed}_${Date.now()}_${batchCount}.png` + console.log(`Saving to ${savePath}\n\n`); + canvas.saveImage(savePath, true); // save the image currently on canvas to a file. + } } function getSeed(oldSeed){ From 2846afb5cc905bccfa25124efb70a15caad4628a Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Thu, 24 Oct 2024 16:58:51 -0700 Subject: [PATCH 10/13] Cleaned up iteration logic to handle edge cases. Added preliminary support for customizing saved filenames. LoRA lookup bugfixes. --- scripts/dynamic-prompts/dynamic-prompts.js | 212 ++++++++++++++++----- 1 file changed, 166 insertions(+), 46 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index c250d88..ec409b6 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -41,16 +41,15 @@ */ //Version -const versionString = "v3.5.7"; +const versionString = "v3.5.8"; //Maximum iterations for Iterate Mode const maxIter = 500; const DEBUG = false; //store selected prompt data and UI config -let UICONFIG = pipeline.configuration; let userPrompt = ''; let uiPrompt = ''; - -// Default example prompt for UI demonstrating category use +const UICONFIG = pipeline.configuration; +//Default example prompt for UI demonstrating category use const defaultPrompt = "wide-angle shot of {weather} {time} {locale}" /* These are the prompts randomly selected from if UI Prompt isn't valid. @@ -309,11 +308,12 @@ if (!iterateMode){ for (let i = 0; i < batchCount; i++){ let batchCountLog = `Rendering ${i + 1} of ${batchCount}`; console.warn(batchCountLog); - render(getPrompt(), i); + render(getDynamicPrompt(), i); } } else { - let promptData = getPrompt(); - p = computeTotalPromptCount(promptData.prompt); + const promptData = getDynamicPrompt(); + const dynPrompt = promptData.prompt; + p = computeTotalPromptCount(dynPrompt); if (p > maxIter){ console.warn(`Max iterations of ${maxIter} exceeded: Prompt total combinations = ${p}\n`); console.warn("Reduce the number of categories used in prompt."); @@ -331,34 +331,105 @@ if (!iterateMode){ } function computeTotalPromptCount(dynamicPrompt) { - let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); - let totalCombinationCount = 1; + // Find all unique top-level placeholders in the prompt + const regex = /{(\w+)}/g; + let match; + let placeholders = new Set(); + while ((match = regex.exec(dynamicPrompt)) !== null) { + placeholders.add(match[1]); + } + + // If no placeholders, return 1 + if (placeholders.size === 0) { + return 1; + } + + let totalCombinations = 1; + for (let placeholder of placeholders) { - const valueCount = categories[placeholder].length; // Get the number of values in each category - totalCombinationCount *= valueCount; // Multiply the counts to calculate the maximum possible number of combinations + const optionsCount = countPlaceholderOptions(placeholder, new Set()); + totalCombinations *= optionsCount; } - return totalCombinationCount; + + return totalCombinations; +} + +function countPlaceholderOptions(placeholder, seenPlaceholders) { + if (seenPlaceholders.has(placeholder)) { + throw new Error(`Circular reference detected for placeholder '{${placeholder}}'`); + } + + seenPlaceholders.add(placeholder); + + const values = categories[placeholder]; + if (!values) { + throw new Error(`Category '${placeholder}' not defined.`); + } + + let totalOptions = 0; + + for (let value of values) { + const optionsCount = countValueOptions(value, new Set(seenPlaceholders)); + totalOptions += optionsCount; + } + + seenPlaceholders.delete(placeholder); + + return totalOptions; +} + +function countValueOptions(value, seenPlaceholders) { + // Find all placeholders in the value + const regex = /{(\w+)}/g; + let match; + let placeholders = new Set(); + while ((match = regex.exec(value)) !== null) { + placeholders.add(match[1]); + } + + // If no placeholders, return 1 + if (placeholders.size === 0) { + return 1; + } + + let totalCombinations = 1; + + for (let placeholder of placeholders) { + const optionsCount = countPlaceholderOptions(placeholder, seenPlaceholders); + totalCombinations *= optionsCount; + } + + return totalCombinations; } function* generatePrompts(dynamicPrompt) { - function cartesian(...arrays) { - if (arrays.length === 0) return []; - return arrays.reduce((acc, curr) => { - return acc.flatMap(a => curr.map(b => [].concat(a, b))); - }, [[]]); + // Base case: if no placeholders, yield the prompt + if (!/{\w+}/.test(dynamicPrompt)) { + yield dynamicPrompt; + return; + } + + // Find the first placeholder in the prompt + const regex = /{(\w+)}/g; + const match = regex.exec(dynamicPrompt); + + if (!match) { + yield dynamicPrompt; + return; + } + + const placeholder = match[1]; + + const values = categories[placeholder]; + if (!values) { + throw new Error(`Category '${placeholder}' not defined.`); } - let placeholders = dynamicPrompt.match(/{(\w+)}/g).map(p => p.replace(/[{}]/g, '')); - let validPlaceholders = placeholders.filter(p => categories[p]); - let categoryValues = validPlaceholders.map(p => categories[p]); - let combinations = cartesian(...categoryValues); - - for (let combination of combinations) { - let prompt = dynamicPrompt; - validPlaceholders.forEach((placeholder, i) => { - prompt = prompt.replace(`{${placeholder}}`, combination[i]); - }); - yield prompt; + for (const value of values) { + // Replace all occurrences of the placeholder with the value + const newPrompt = dynamicPrompt.replace(new RegExp(`{${placeholder}}`, 'g'), value); + // Recursively generate prompts for the new prompt + yield* generatePrompts(newPrompt); } } @@ -383,7 +454,9 @@ function selectRandomPrompt() { if (downloadModels){ getModels(promptData); } - console.warn(JSON.stringify(promptData)); + if (DEBUG){ + console.warn(JSON.stringify(promptData)); + } return promptData; } @@ -404,19 +477,24 @@ function getModels(promptData){ pipeline.downloadBuiltins(models); } -// Resolves whether LoRAs are filenames or +// Resolves LoRAs to filename function resolveLoras(loras){ const FILESUFFIX = ".ckpt"; + const myLoras = []; for (let i = 0; i < loras.length; i++) { - if (!loras[i].file.endsWith(FILESUFFIX)){ - let myfile = pipeline.findLoRAByName(loras[i].file).file; - if (DEBUG){ - console.log(`Filename ${loras[i].file} resolved to ${JSON.stringify(myfile)}`); + let myLora = loras[i]; + let myname = myLora.file; + if (!myname.endsWith(FILESUFFIX)){ + let myfile = pipeline.findLoRAByName(myname).file; + // Assign resolved name + myLora.file = myfile; + myLoras.push(myLora); + if (DEBUG){ + console.log(`Filename ${myname} resolved to ${myfile}`); + } } - loras[i].file = myfile; } - } - return loras; + return myLoras; } // Function to extract and validate category names and their requested item count or range from the uiPrompt @@ -455,7 +533,7 @@ function isPromptValid(uiPrompt, categories) { } //get prompt each iteration -function getPrompt () { +function getDynamicPrompt () { let promptData = {}; if (useUiPrompt) { console.log("Using UI Prompt"); @@ -482,10 +560,12 @@ function render (promptData, batchCount){ // set generated prompt let generatedPrompt = replaceWildcards(promptData.prompt, categories); let neg; - let finalConfiguration = {}; + let finalConfiguration = Object.create(pipeline.configuration); // Set seed according to user selection let mySeed = getSeed(pipeline.configuration.seed); - console.log(JSON.stringify(UICONFIG)); + if (DEBUG){ + console.log(JSON.stringify(finalConfiguration)); + } if (useUiPrompt){ finalConfiguration = UICONFIG; @@ -497,15 +577,21 @@ function render (promptData, batchCount){ //Apply configuration changes, if any finalConfiguration = Object.assign(UICONFIG, promptData.configuration); finalConfiguration.loras = promptData.configuration.loras; - console.log(finalConfiguration.model); + if (DEBUG){ + console.log(finalConfiguration.model); + } } } // Batch > 1 is no bueno finalConfiguration.batchSize = 1; finalConfiguration.seed = mySeed; - //Clear canvas + canvas.clear(); - console.warn(JSON.stringify(finalConfiguration)); + + if(DEBUG){ + console.warn(JSON.stringify(finalConfiguration)); + } + pipeline.run({ configuration: finalConfiguration, prompt: generatedPrompt, @@ -514,15 +600,49 @@ function render (promptData, batchCount){ //Output render time elapsed timer(start); //Save Image if enabled + savetoImageDir(finalConfiguration, batchCount); +} + +function savetoImageDir(config, batchCount){ if(outputDir) { - // working around metadata bug by forcing our config onto the UI before saving - pipeline.configuration = finalConfiguration; - let savePath = `${outputDir}/${finalConfiguration.seed}_${Date.now()}_${batchCount}.png` + // worked around metadata bug by forcing our config onto the UI before saving + // but that didn't work consistently so trying something else + // Crazy thing is the metadata is borked with canvas.save, but fine + // if you reload the image in DT + const SamplerTypeReverse = Object.fromEntries( + Object.entries(SamplerType).map(([key, value]) => [value, key]) + ); + const seed = config.seed; + const model = new String(sanitize(config.model.replace('.ckpt'))).slice(0, 8); + const sampler = sanitize(SamplerTypeReverse[config.sampler]).slice(0, 8); + const steps = config.steps; + const time = getTimeString(); + let savePath = `${outputDir}/${model}_${sampler}_${steps}_${time}_${batchCount}.png` console.log(`Saving to ${savePath}\n\n`); canvas.saveImage(savePath, true); // save the image currently on canvas to a file. } } +function sanitize(text) { + // Replace characters that are not allowed in filenames with a hyphen + return text + .trim() // Remove leading/trailing whitespace + .toLowerCase() // Convert to lowercase for consistency + .replace(/[\/\\?%*:|"<>]/g, '-') // Replace invalid characters + .replace(/\s+/g, '_'); // Replace spaces with underscores +} + +function getTimeString() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + + return `${year}${month}${day}${hours}${minutes}`; +} + function getSeed(oldSeed){ const MAX_INT_32 = 2147483647; let seed = 0; From 702698015eba7ffbf377b47eed0c76afe51b24f0 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 25 Oct 2024 04:24:38 -0700 Subject: [PATCH 11/13] bugfixes for prompts objects missing fields --- scripts/dynamic-prompts/dynamic-prompts.js | 70 ++++++++++++++-------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index ec409b6..5876e4d 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author: zanshinmu -// v3.5.7 +// v3.5.8 // Discord Thread for Dynamic Prompts: // https://discord.com/channels/1038516303666876436/1207467278426177736 /** @@ -95,13 +95,12 @@ const prompts = [ { prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", - model: "RealVisXL v4.0", + model: "FLUX.1 [schnell] (8-bit)", loras: [ - { file: "Fix Hands Slider", weight: 0.3 }, - { file: "Pixel Art XL v1.1", weight: 0.4 } - ], + { file: "AntiBlur v1.0 [dev]", weight: 0.4} + ], configuration: - {width:1024,height:1024,steps:28,sampler:0,guidanceScale:4.0} + {width:1152,height:896,steps:2,sampler:10,guidanceScale:2.5,clipLText:"Photograph, sensual, professional",resolutionDependentShift:true} }, { prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", @@ -440,23 +439,31 @@ function selectRandomPrompt() { // Get the randomly selected prompt object const selectedPrompt = prompts[randomIndex]; // Extract prompt string, LoRa filenames, and weights - let myprompt = selectedPrompt.prompt; - let myneg = selectedPrompt.negativePrompt; - let mymodel = selectedPrompt.model; - let myconfig = selectedPrompt.configuration; + const myprompt = selectedPrompt.prompt; + const myneg = selectedPrompt.negativePrompt; + const mymodel = selectedPrompt.model; + const loras = []; + let myconfig = {}; + if (typeof selectedPrompt.configuration !== 'undefined'){ + myconfig = selectedPrompt.configuration; + } else { + myconfig = {...UICONFIG}; + } //Prepare LoRAs const myLoras = resolveLoras(selectedPrompt.loras); - const loras = myLoras.map(lora => ({ file: lora.file, weight: lora.weight })); + if (typeof myLoras !== 'undefined'){ + myconfig.loras = myLoras.map(lora => ({ file: lora.file, weight: lora.weight })); + } myconfig.model = mymodel; - myconfig.loras = loras; // Store the promptData object - let promptData = { prompt: myprompt, negativePrompt: myneg, configuration: myconfig }; + const promptData = { prompt: myprompt, negativePrompt: myneg, configuration: myconfig }; if (downloadModels){ getModels(promptData); } if (DEBUG){ console.warn(JSON.stringify(promptData)); } + //throw new Error("BUTTS"); return promptData; } @@ -464,14 +471,16 @@ function selectRandomPrompt() { function getModels(promptData){ let models = []; //Model first - models.push(promptData.configuration.model); - //Initiate Download - pipeline.downloadBuiltins(models); + if (typeof promptData.configuration.model !== 'undefined'){ + models.push(promptData.configuration.model); + } //Loras - for (let i = 0; i < promptData.configuration.loras.length; i++) { - let lora = promptData.configuration.loras[i].file; - models.push(lora); + if (typeof promptData.configuration.loras !== 'undefined'){ + for (let i = 0; i < promptData.configuration.loras.length; i++) { + let lora = promptData.configuration.loras[i].file; + models.push(lora); + } } //Initiate Download pipeline.downloadBuiltins(models); @@ -479,12 +488,15 @@ function getModels(promptData){ // Resolves LoRAs to filename function resolveLoras(loras){ + if (!loras){ + return loras; + } const FILESUFFIX = ".ckpt"; const myLoras = []; for (let i = 0; i < loras.length; i++) { let myLora = loras[i]; let myname = myLora.file; - if (!myname.endsWith(FILESUFFIX)){ + if (!myname.endsWith(FILESUFFIX) && myname){ let myfile = pipeline.findLoRAByName(myname).file; // Assign resolved name myLora.file = myfile; @@ -613,8 +625,11 @@ function savetoImageDir(config, batchCount){ Object.entries(SamplerType).map(([key, value]) => [value, key]) ); const seed = config.seed; - const model = new String(sanitize(config.model.replace('.ckpt'))).slice(0, 8); - const sampler = sanitize(SamplerTypeReverse[config.sampler]).slice(0, 8); + let model = "undefined"; + if (typeof config.model !== 'undefined'){ + model = sanitize(config.model.replace('.ckpt')); + } + const sampler = sanitize(SamplerTypeReverse[config.sampler]); const steps = config.steps; const time = getTimeString(); let savePath = `${outputDir}/${model}_${sampler}_${steps}_${time}_${batchCount}.png` @@ -624,12 +639,19 @@ function savetoImageDir(config, batchCount){ } function sanitize(text) { + // Handle null case + if (typeof text === 'undefined'){ + let undef = "undefined"; + return undef; + } + const trunc = 16; // Replace characters that are not allowed in filenames with a hyphen return text .trim() // Remove leading/trailing whitespace .toLowerCase() // Convert to lowercase for consistency - .replace(/[\/\\?%*:|"<>]/g, '-') // Replace invalid characters - .replace(/\s+/g, '_'); // Replace spaces with underscores + .replace(new RegExp('[\/\\\\?%*:|"<>[]{}$#@&+;,.~()]', 'g'), '-') // Comprehensive now + .replace(/\s+/g, '_') // Replace spaces with underscores + .slice(0, trunc) // Truncate to trunc chars; } function getTimeString() { From 9eab12e016c07bb17148093e9e5b85ad39caaedf Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 25 Oct 2024 15:10:45 -0700 Subject: [PATCH 12/13] Added debug printer class, final bugfixes for release --- scripts/dynamic-prompts/dynamic-prompts.js | 102 ++++++++++++++++----- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index 5876e4d..c2aac30 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -42,9 +42,61 @@ //Version const versionString = "v3.5.8"; -//Maximum iterations for Iterate Mode +//Maximum iterations for Iterate Mode, this is a good value for everything +//Macs with more resources can probably set this much higher const maxIter = 500; + +//Sure, you can turn this on if you like your console cluttered. :P const DEBUG = false; + +class DebugPrint { + static Level = Object.freeze({ + INFO: 'INFO', + WARN: 'WARN', + ERROR: 'ERROR' + }); + + #debugMode; // Private field to store debug mode state + + /** + * Constructor for DebugPrint + * @param {boolean} [debugMode=false] - Whether to enable debug printing by default + */ + constructor(debugMode = false) { + this.#debugMode = debugMode; + } + + /** + * Prints a message if debug mode is enabled + * @param {string} message - The message to print + * @param {DebugPrint.Level} [level=DebugPrint.Level.INFO] - The log level of the message + */ + print(message, level = DebugPrint.Level.INFO) { + if (!Object.values(DebugPrint.Level).includes(level)) { + throw new Error(`Invalid log level: ${level}`); + } + + if (this.#debugMode) { + switch (level) { + case DebugPrint.Level.INFO: + console.log(`${message}`); + break; + + case DebugPrint.Level.WARN: + console.warn(`${message}`); + break; + + case DebugPrint.Level.ERROR: + console.error(`${message}`); + break; + + } + } + } +} + +// Initiate debug logger, set state to DEBUG; +const debug = new DebugPrint(DEBUG); //store selected prompt data and UI config let userPrompt = ''; let uiPrompt = ''; @@ -83,7 +135,7 @@ const defaultPrompt = "wide-angle shot of {weather} {time} {locale}" const prompts = [ { - prompt: "{colors:1-3} dominant {camera} shot of a {adjective} cyborg woman, {style}, {hairstyle} {haircolor} hair, {features}, {pose} in {weather} {time} {locale}", + prompt: "{colors:1-3} dominant {camera} shot of a {cyborg}, {pose} in {weather} {time} {locale}", negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", model: "FLUX.1 [schnell] (8-bit)", loras: [ @@ -93,7 +145,7 @@ const prompts = [ {width:1152,height:896,steps:2,sampler:10,guidanceScale:2.5,clipLText:"Photograph, sensual, professional",resolutionDependentShift:true} }, { - prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", + prompt: "{camera} shot of a {cyborg}, {pose} in {weather} {time} {locale}", negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", model: "FLUX.1 [schnell] (8-bit)", loras: [ @@ -103,7 +155,7 @@ const prompts = [ {width:1152,height:896,steps:2,sampler:10,guidanceScale:2.5,clipLText:"Photograph, sensual, professional",resolutionDependentShift:true} }, { - prompt: "{camera} shot of a {adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}, {pose} in {weather} {time} {locale}", + prompt: "{camera} shot of a {cyborg}, {pose} in {weather} {time} {locale}", negativePrompt: "NSFW, nude, blurry, 3d, drawing, anime", model: "RealVisXL v4.0", loras: [ @@ -120,6 +172,10 @@ const prompts = [ // Categories definition const categories = { + cyborg: [ + "{adjective} cyborg man, {hairstyle} {haircolor} hair, {features}, wearing {malestyle}", + "{adjective} cyborg woman, {hairstyle} {haircolor} hair, {features} wearing {style}" + ], time: [ "morning", "noon", @@ -272,6 +328,8 @@ if (iterateMode){ console.log("Iterate Mode"); } + + if (useUiPrompt) { const userSelection = requestFromUser("Dynamic Prompts: UI Prompt", okButton, function() { return [ @@ -460,10 +518,7 @@ function selectRandomPrompt() { if (downloadModels){ getModels(promptData); } - if (DEBUG){ - console.warn(JSON.stringify(promptData)); - } - //throw new Error("BUTTS"); + debug.print(JSON.stringify(promptData), DebugPrint.Level.WARN); return promptData; } @@ -482,13 +537,18 @@ function getModels(promptData){ models.push(lora); } } - //Initiate Download - pipeline.downloadBuiltins(models); + if (!pipeline.areModelsDownloaded(models)){ + //Initiate Download + debug.print(`${JSON.stringify(models)} not clean, initiating download.`, DebugPrint.Level.WARN); + pipeline.downloadBuiltins(models); + } else { + debug.print(`${JSON.stringify(models)} clean, not initiating download.`, DebugPrint.Level.WARN); + } } // Resolves LoRAs to filename function resolveLoras(loras){ - if (!loras){ + if (typeof loras === 'undefined'){ return loras; } const FILESUFFIX = ".ckpt"; @@ -496,15 +556,13 @@ function resolveLoras(loras){ for (let i = 0; i < loras.length; i++) { let myLora = loras[i]; let myname = myLora.file; - if (!myname.endsWith(FILESUFFIX) && myname){ + if (!myname.endsWith(FILESUFFIX)){ let myfile = pipeline.findLoRAByName(myname).file; // Assign resolved name myLora.file = myfile; - myLoras.push(myLora); - if (DEBUG){ - console.log(`Filename ${myname} resolved to ${myfile}`); - } + debug.print(`Filename ${myname} resolved to ${myfile}`); } + myLoras.push(myLora); } return myLoras; } @@ -575,9 +633,7 @@ function render (promptData, batchCount){ let finalConfiguration = Object.create(pipeline.configuration); // Set seed according to user selection let mySeed = getSeed(pipeline.configuration.seed); - if (DEBUG){ - console.log(JSON.stringify(finalConfiguration)); - } + debug.print(JSON.stringify(finalConfiguration)); if (useUiPrompt){ finalConfiguration = UICONFIG; @@ -589,9 +645,7 @@ function render (promptData, batchCount){ //Apply configuration changes, if any finalConfiguration = Object.assign(UICONFIG, promptData.configuration); finalConfiguration.loras = promptData.configuration.loras; - if (DEBUG){ - console.log(finalConfiguration.model); - } + debug.print(finalConfiguration.model); } } // Batch > 1 is no bueno @@ -600,9 +654,7 @@ function render (promptData, batchCount){ canvas.clear(); - if(DEBUG){ - console.warn(JSON.stringify(finalConfiguration)); - } + debug.print(JSON.stringify(finalConfiguration), DebugPrint.Level.WARN); pipeline.run({ configuration: finalConfiguration, From f87f43d6ccdf9fac12b3b16641629de5e413d8c4 Mon Sep 17 00:00:00 2001 From: David Van de Ven Date: Fri, 15 Nov 2024 14:12:44 -0800 Subject: [PATCH 13/13] Fixed model downloading. Added total rendering time. --- scripts/dynamic-prompts/dynamic-prompts.js | 76 ++++++++++++---------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/scripts/dynamic-prompts/dynamic-prompts.js b/scripts/dynamic-prompts/dynamic-prompts.js index c2aac30..9b97527 100644 --- a/scripts/dynamic-prompts/dynamic-prompts.js +++ b/scripts/dynamic-prompts/dynamic-prompts.js @@ -1,7 +1,7 @@ //@api-1.0 // dynamic prompts // author: zanshinmu -// v3.5.8 +// v3.5.9 // Discord Thread for Dynamic Prompts: // https://discord.com/channels/1038516303666876436/1207467278426177736 /** @@ -41,7 +41,7 @@ */ //Version -const versionString = "v3.5.8"; +const versionString = "v3.5.9"; //Maximum iterations for Iterate Mode, this is a good value for everything //Macs with more resources can probably set this much higher const maxIter = 500; @@ -362,11 +362,14 @@ if(useUiPrompt){ // Main batch loop if (!iterateMode){ + const bstart = Date.now(); + const bmessage = "✔︎ Total render time ‣"; for (let i = 0; i < batchCount; i++){ let batchCountLog = `Rendering ${i + 1} of ${batchCount}`; console.warn(batchCountLog); render(getDynamicPrompt(), i); } + elapsed(bstart, message = bmessage); } else { const promptData = getDynamicPrompt(); const dynPrompt = promptData.prompt; @@ -377,6 +380,8 @@ if (!iterateMode){ } else { let k = 1; console.log(`Iterating over dynamic prompt:\n '${dynPrompt}'\n Total combinations number ${p}.`); + const istart = Date.now(); + const imessage = "✔︎ Total iteration time ‣"; for (let generatedPrompt of generatePrompts(dynPrompt)) { let myConfig = promptData; promptData.prompt = dynPrompt; @@ -384,6 +389,7 @@ if (!iterateMode){ render(generatedPrompt, k); k++; } + elapsed(istart, message = imessage); } } @@ -512,41 +518,26 @@ function selectRandomPrompt() { if (typeof myLoras !== 'undefined'){ myconfig.loras = myLoras.map(lora => ({ file: lora.file, weight: lora.weight })); } - myconfig.model = mymodel; + myconfig.model = resolveModel(mymodel); // Store the promptData object const promptData = { prompt: myprompt, negativePrompt: myneg, configuration: myconfig }; - if (downloadModels){ - getModels(promptData); - } - debug.print(JSON.stringify(promptData), DebugPrint.Level.WARN); + debug.print(JSON.stringify(promptData), DebugPrint.Level.WARN); return promptData; } -//Download models if necessary -function getModels(promptData){ - let models = []; - //Model first - if (typeof promptData.configuration.model !== 'undefined'){ - models.push(promptData.configuration.model); - } - - //Loras - if (typeof promptData.configuration.loras !== 'undefined'){ - for (let i = 0; i < promptData.configuration.loras.length; i++) { - let lora = promptData.configuration.loras[i].file; - models.push(lora); - } - } - if (!pipeline.areModelsDownloaded(models)){ - //Initiate Download - debug.print(`${JSON.stringify(models)} not clean, initiating download.`, DebugPrint.Level.WARN); - pipeline.downloadBuiltins(models); +function resolveModel(model){ + if(downloadModels){ + let myNameArray = []; + myNameArray.push(model); + pipeline.downloadBuiltins(myNameArray); + debug.print(`Model ${model} downloaded`); } else { - debug.print(`${JSON.stringify(models)} clean, not initiating download.`, DebugPrint.Level.WARN); + debug.print('Download models disabled.'); } + return model; } -// Resolves LoRAs to filename +// Resolves LoRAs to filenames function resolveLoras(loras){ if (typeof loras === 'undefined'){ return loras; @@ -557,13 +548,24 @@ function resolveLoras(loras){ let myLora = loras[i]; let myname = myLora.file; if (!myname.endsWith(FILESUFFIX)){ + try{ let myfile = pipeline.findLoRAByName(myname).file; // Assign resolved name myLora.file = myfile; - debug.print(`Filename ${myname} resolved to ${myfile}`); + debug.print(`Filename ${myname} resolved to ${JSON.stringify(myfile)}`); + } catch (e){ + debug.print(`${e} Is it downloaded?`); + if(downloadModels){ + let myNameArray = []; + myNameArray.push(myname); + myLora.file = pipeline.downloadBuiltins(myNameArray); + } else { + myLora.file = myname; + } } - myLoras.push(myLora); } + myLoras.push(myLora); + } return myLoras; } @@ -614,13 +616,16 @@ function getDynamicPrompt () { return promptData; } -function timer (start){ +function elapsed (start, message = "✔︎ Render time ‣"){ const end = Date.now(); const duration = end - start; - const minutes = Math.floor(duration / 60000); + const hours = Math.floor(duration / 3600000); + const minutes = Math.floor((duration % 3600000) / 60000); // calculate the remaining minutes after subtracting the elapsed hours from the total duration let seconds = Math.floor((duration % 60000) / 1000); - seconds = seconds < 10 ? '0' + seconds : seconds; - console.log(`✔︎ Render time ‣ ${minutes}:${seconds}\n`); + const formattedHours = String(hours).padStart(2, '0'); + const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedSeconds = String(seconds).padStart(2, '0'); + console.log(`${message} ${formattedHours}:${formattedMinutes}:${formattedSeconds}\n`); } // Run pipeline @@ -655,14 +660,13 @@ function render (promptData, batchCount){ canvas.clear(); debug.print(JSON.stringify(finalConfiguration), DebugPrint.Level.WARN); - pipeline.run({ configuration: finalConfiguration, prompt: generatedPrompt, negativePrompt: neg }); //Output render time elapsed - timer(start); + elapsed(start); //Save Image if enabled savetoImageDir(finalConfiguration, batchCount); }