# this imports the code from files and modules import description_helper as dsh import gradio as gr import sd_generator as sd import utilities as u import process_html import process_text import os import ctypes import tripo3d as tripo3d # This is a fix for the way that python doesn't release system memory back to the OS and it was leading to locking up the system libc = ctypes.cdll.LoadLibrary("libc.so.6") M_MMAP_THRESHOLD = -3 # Set malloc mmap threshold. libc.mallopt(M_MMAP_THRESHOLD, 2**20) # Declare accessible directories base_dir = os.path.dirname(os.path.abspath(__file__)) # Gets the directory where the script is located print(f"Base Directory :",base_dir) list_of_static_dir = [os.path.join(base_dir, "output"), os.path.join(base_dir, "dependencies"), os.path.join(base_dir, "galleries/examples/statblocks")] gr.set_static_paths(paths=list_of_static_dir) style_css = custom_css = """ """ # Build gradio app with gr.Blocks(css = "style.css") as demo: # Functions and State Variables mon_name = gr.State() mon_size = gr.State() mon_type = gr.State() mon_subtype = gr.State() mon_alignment = gr.State() mon_armor_class = gr.State() mon_hp = gr.State() mon_hit_dice = gr.State() mon_speed = gr.State() mon_abilities = gr.State() mon_saving_throws = gr.State() mon_skills = gr.State() mon_damage_resistance = gr.State() mon_senses = gr.State() mon_languages = gr.State() mon_challenge_rating = gr.State() mon_xp = gr.State() mon_actions = gr.State() mon_cantrips = gr.State() mon_spells = gr.State() mon_spell_slots = gr.State() mon_legendary_actions = gr.State() mon_description = gr.State() mon_sd_prompt = gr.State() # Image Variables generated_image_list = gr.State([]) selected_generated_image = gr.State() selected_seed_image = gr.State() selected_token_image = gr.State() # Take input from gradio user and call the llm in description_helper def gen_mon_desc(user_monster_text, spellcaster, legendary_actions): # declare cantrips, spells, and spell slots as empty string unless the variable is changed. mon_cantrips = "" mon_spells = "" mon_spell_slots = "" mon_legendary_actions = "" llm_output = dsh.call_llm_and_cleanup(user_monster_text, spellcaster, legendary_actions) user_monster = dsh.convert_to_dict(llm_output) user_monster = llm_output keys_list = list(user_monster) mon_name = user_monster['name'] mon_size = user_monster['size'] mon_type = user_monster['type'] mon_subtype = user_monster['subtype'] mon_alignment = user_monster['alignment'] mon_armor_class = user_monster['armor_class'] mon_hp = user_monster['hit_points'] mon_hit_dice = user_monster['hit_dice'] mon_speed = user_monster['speed'] if type(mon_speed) == dict: mon_speed = process_text.format_mon_qualities(mon_speed) mon_abilities = process_text.format_abilities_for_editing(user_monster['abilities']) mon_saving_throws = user_monster['saving_throws'] if type(mon_saving_throws) == dict: mon_saving_throws = process_text.format_mon_qualities(mon_saving_throws) mon_skills = user_monster['skills'] if type(mon_skills) == dict: mon_skills = process_text.format_mon_qualities(mon_skills) mon_damage_resistance = user_monster['damage_resistance'] if type(mon_damage_resistance) == dict: mon_damage_resistance = process_text.format_mon_qualities(mon_damage_resistance) mon_senses = user_monster['senses'] if type(mon_senses) == dict: mon_senses = process_text.format_mon_qualities(mon_senses) mon_languages = user_monster['languages'] mon_challenge_rating = user_monster['challenge_rating'] mon_xp = user_monster['xp'] if 'actions' in keys_list: mon_actions = process_text.format_actions_for_editing(user_monster['actions']) if "spells" in keys_list : print(user_monster['spells']) print(f"Length of spells : {len(user_monster['spells'])}") if len(user_monster['spells']) >= 1 and user_monster['spells'] != "{}": mon_spells = user_monster['spells'] mon_cantrips,mon_spells,mon_spell_slots = process_text.format_spells_for_editing(mon_spells) print(mon_cantrips,mon_spells, mon_spell_slots) if 'legendary_actions' in keys_list and len(user_monster['legendary_actions']) >= 1 and user_monster['legendary_actions'] != "{}": mon_legendary_actions = user_monster['legendary_actions'] if type(mon_legendary_actions) == dict: mon_legendary_actions = process_text.format_legendaries_for_editing(mon_legendary_actions) mon_description = user_monster['description'] mon_sd_prompt = user_monster['sd_prompt'] #Return each State variable twice, once to the variable and once to the textbox return [mon_name,mon_name, mon_size,mon_size, mon_type,mon_type, mon_subtype,mon_subtype, mon_alignment, mon_alignment, mon_armor_class, mon_armor_class, mon_hp, mon_hp, mon_hit_dice, mon_hit_dice, mon_speed, mon_speed, mon_abilities,mon_abilities, mon_saving_throws, mon_saving_throws, mon_skills, mon_skills, mon_damage_resistance, mon_damage_resistance, mon_senses, mon_senses, mon_languages, mon_languages, mon_challenge_rating, mon_challenge_rating, mon_xp, mon_xp, mon_actions, mon_actions, mon_cantrips, mon_cantrips, mon_spells, mon_spells,mon_spell_slots,mon_spell_slots, mon_legendary_actions, mon_legendary_actions, mon_description, mon_description, mon_sd_prompt,mon_sd_prompt ] #Function to dynamically render textbox if it has text. def update_visibility(textbox): if not textbox: return gr.update(visible=False) return gr.update(visible=True) # Called on user selecting an image from the gallery, outputs the path of the image def assign_img_path(evt: gr.SelectData): img_dict = evt.value print(img_dict) selected_image_path = img_dict['image']['url'] print(selected_image_path) return selected_image_path # Make a list of files in image_temp and delete them def delete_temp_images(): image_list = u.directory_contents('./image_temp') u.delete_files(image_list) #img2img.image_list.clear() # Called when pressing button to generate image, updates gallery by returning the list of image URLs def generate_image_update_gallery(image_prompt,image_name, token = False): delete_temp_images() print(f"sd_prompt is a {type(image_prompt)}") image_list = [] num_img = 4 for x in range(num_img): preview = sd.preview_and_generate_image(image_name, image_prompt,token) image_list.append(preview) yield image_list del preview return image_list def generate_token_update_gallery(image_prompt,image_name,token = True): delete_temp_images() print(f"sd_prompt is a {type(image_prompt)}") image_list = [] num_img = 4 for x in range(num_img): preview = sd.preview_and_generate_image(image_name, image_prompt,token) image_list.append(preview) yield image_list del preview return image_list # Generate a 3D model by passing the chosen token image to Tripo3D.ai API def generate_model_update_gallery(image): generated_model = tripo3d.generate_model(image) return generated_model # Build html text by processing the generated dictionaries. def build_html_file(mon_name_output, mon_size_output, mon_type_output, mon_subtype_output, mon_alignment_output, mon_armor_class_output, mon_hp_output, mon_hit_dice_output, mon_speed_output, mon_abilities, mon_saving_throws_output, mon_skills_output, mon_damage_resistance_output, mon_senses_output, mon_languages_output, mon_challenge_rating_output, mon_xp_output, mon_actions_output, selected_generated_image, mon_description_output, mon_cantrips_output, mon_spells_output, mon_spell_slot_output, mon_legendary_actions_output ): mon_file_path = process_html.build_html_base( mon_name_output, mon_size_output, mon_type_output, mon_subtype_output, mon_alignment_output, mon_armor_class_output, mon_hp_output, mon_hit_dice_output, mon_speed_output, mon_abilities, mon_saving_throws_output, mon_skills_output, mon_damage_resistance_output, mon_senses_output, mon_languages_output, mon_challenge_rating_output, mon_xp_output, mon_actions_output, selected_generated_image, mon_description_output, mon_cantrips_output, mon_spells_output, mon_spell_slot_output, mon_legendary_actions_output, ) mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html' if not os.path.exists(mon_file_path): print(f"{mon_file_path} not found") else: print(f"{mon_file_path} found") iframe = iframe = f"""""" return iframe # Build the html and file path to pass to gradio to output html file in gr.html def gen_link(): mon_file_path = u.file_name_list[0]+'/' + u.file_name_list[1] +'.html' if not os.path.exists(mon_file_path): print(f"{mon_file_path} not found") else: print(f"{mon_file_path} found") iframe = iframe = f"""""" link = f'{u.file_name_list[1] +".html"}' return iframe with gr.Tab("Instructions"): gr.HTML("""

Monster Statblock Generator

With this AI driven tool you will build a collectible style card of a fantasy flavored item with details.

""") markdown_instructions = """## How It Works This tool is a fun way to quickly generate Dungeons and Dragons monster manual style statblocks with art and a token for a Virtual Table Top. \n 1. Your intitial text along with the prompt is sent to GPT 4o to generate all the values for a Dungeons and Dragons creature. \n a. Include as much or as little information as you'd like. \n b. Just a name : The Flavor Lich \n c. A bit of detail : A friendly skeletal lich who is a master of flavor, called The Flavor Lich \n d. Lots of detail : A friendly skeletal lich who is a master of flavor, called The Flavor Lich, the Lich is Challenge Rating 8 and is a 4th level spell caster whose spells are all about food. \n 2. The results will populate below in editable fields that are saved on edit. \n 3. Review the results, make any changes you'd like. \n ## The first image generation take about 2 minutes to 'warm up' after that it's ~10s per image. \n \n **Image and Text Generation**: Now you can generate 4 images for the statblock page without text and pick your favorite. \n 4. Click 'Generate Statblock Art' and wait for the images to generate, then select the one you'd like to use. \n 5. Click 'Generate HTML' to generate a webpage that can be saved or printed as PDF. \n 6. Last, you can generate a token or figure of your creature or a 3d model to download. \n """ gr.Markdown(markdown_instructions) with gr.Tab("Generator"): with gr.Row(): with gr.Column(): user_mon_description = gr.Textbox(label = "Step 1 : Write a description and give a name or type to your creation!", lines = 1, placeholder=f"Ex : A friendly skeletal lich who is a master of flavor, called The Flavor Lich", elem_id= "custom-textbox") with gr.Column(): spells_checkbox = gr.Checkbox(label= "Spellcaster?") legendary_action_checkbox = gr.Checkbox(label= "Legendary Actions?") desc_gen = gr.Button(value = "Click to Generate Description") mon_description_output = gr.Textbox(label = 'Description', lines = 2, interactive=True, visible=False) mon_description_output.change(fn=update_visibility, inputs=[mon_description_output], outputs=[mon_description_output]) with gr.Row(): with gr.Column(scale = 1): mon_name_output = gr.Textbox(label = 'Name', lines = 1, interactive=True, visible=False) mon_name_output.change(fn=update_visibility, inputs=[mon_name_output], outputs=[mon_name_output]) mon_size_output = gr.Textbox(label = 'Size', lines = 1, interactive=True, visible=False) mon_size_output.change(fn=update_visibility, inputs=[mon_size_output], outputs=[mon_size_output]) mon_alignment_output = gr.Textbox(label = 'Alignment', lines = 1, interactive=True, visible=False) mon_alignment_output.change(fn=update_visibility, inputs=[mon_alignment_output], outputs=[mon_alignment_output]) mon_armor_class_output = gr.Textbox(label = 'Armor Class', lines = 1, interactive=True, visible=False) mon_armor_class_output.change(fn=update_visibility, inputs=[mon_armor_class_output], outputs=[mon_armor_class_output]) mon_hit_dice_output = gr.Textbox(label = 'Hit Dice', lines = 1, interactive=True, visible=False) mon_hit_dice_output.change(fn=update_visibility, inputs=[mon_hit_dice_output], outputs=[mon_hit_dice_output]) mon_senses_output = gr.Textbox(label = 'Senses', lines = 1, interactive=True, visible =False) mon_senses_output.change(fn=update_visibility, inputs=[mon_senses_output], outputs=[mon_senses_output]) mon_actions_output = gr.Textbox(label = 'Actions', lines = 16, interactive=True, visible = False) mon_actions_output.change(fn=update_visibility, inputs=[mon_actions_output], outputs=[mon_actions_output]) with gr.Column(scale = 1): mon_type_output = gr.Textbox(label = 'Type', lines = 1, interactive=True, visible=False) mon_actions_output.change(fn=update_visibility, inputs=[mon_actions_output], outputs=[mon_actions_output]) mon_speed_output = gr.Textbox(label = 'Speed', lines = 1, interactive=True, visible=False) mon_speed_output.change(fn=update_visibility, inputs=[mon_speed_output], outputs=[mon_speed_output]) mon_abilities_output = gr.Textbox(label ='Ability Scores', lines = 5, interactive=True, visible=False) mon_abilities_output.change(fn=update_visibility, inputs=[mon_abilities_output], outputs=[mon_abilities_output]) mon_damage_resistance_output = gr.Textbox(label = 'Damage Resistance', lines = 1, interactive=True, visible=False) mon_damage_resistance_output.change(fn=update_visibility, inputs=[mon_damage_resistance_output], outputs=[mon_damage_resistance_output]) mon_challenge_rating_output = gr.Textbox(label = 'Challenge Rating', lines = 1, interactive=True, visible=False) mon_cantrips_output = gr.Textbox(label = 'Cantrips', lines = 16, interactive=True, visible=False) mon_cantrips_output.change(fn=update_visibility, inputs=[mon_cantrips_output], outputs=[mon_cantrips_output]) mon_spells_output = gr.Textbox(label = 'Spells', lines = 16, interactive=True, visible=False) mon_spells_output.change(fn=update_visibility, inputs=[mon_spells_output], outputs=[mon_spells_output]) mon_spell_slot_output = gr.Textbox(label = 'Spell Slots', lines = 8, interactive=True, visible=False) mon_spell_slot_output.change(fn=update_visibility, inputs=[mon_spell_slot_output], outputs=[mon_spell_slot_output]) with gr.Column(scale = 1): mon_subtype_output = gr.Textbox(label = 'Subtype', lines = 1, interactive=True, visible = False) mon_subtype_output.change(fn=update_visibility, inputs=[mon_subtype_output], outputs=[mon_subtype_output]) mon_saving_throws_output = gr.Textbox(label = 'Saving Throws', lines = 1, interactive=True, visible=False) mon_saving_throws_output.change(fn=update_visibility, inputs=[mon_saving_throws_output], outputs=[mon_saving_throws_output]) mon_skills_output = gr.Textbox(label = 'Skills', lines = 1, interactive=True, visible=False) mon_skills_output.change(fn=update_visibility, inputs=[mon_skills_output], outputs=[mon_skills_output]) mon_hp_output = gr.Textbox(label = 'Health Points', lines = 1, interactive=True, visible=False) mon_hp_output.change(fn=update_visibility, inputs=[mon_hp_output], outputs=[mon_hp_output]) mon_languages_output = gr.Textbox(label = 'Languages', lines = 1, interactive=True, visible=False) mon_languages_output.change(fn=update_visibility, inputs=[mon_languages_output], outputs=[mon_languages_output]) mon_xp_output = gr.Textbox(label = 'XP', lines = 1, interactive=True, visible=False) mon_xp_output.change(fn=update_visibility, inputs=[mon_xp_output], outputs=[mon_xp_output]) mon_legendary_actions_output = gr.Textbox(label = 'Legendary Actions', lines = 16, interactive=True, visible = False) mon_legendary_actions_output.change(fn = update_visibility, inputs =[mon_legendary_actions_output], outputs=[mon_legendary_actions_output]) mon_sd_prompt_output = gr.Textbox(label = 'Image Generation Prompt', lines = 1, interactive=True, visible=False) mon_sd_prompt_output.change(fn=update_visibility, inputs=[mon_sd_prompt_output], outputs=[mon_sd_prompt_output]) image_gen_instructions = """ ## Image Generation \n 1. Review the text in the 'Image Generation Prompt' Textbox\n 2. Click 'Generate Statblock Art' \n 3. This will take 2 minutes for the first image, then about 10 seconds each. \n ** *Additional generation will take about 10 seconds each, until the server goes to sleep ~3 minutes inactivity. \n 4. Click your favorite of the four images, this loads it for the page builder and 3d model generator. """ gr.Markdown(image_gen_instructions) with gr.Row(): with gr.Column(): image_gen = gr.Button(value = "Generate Statblock Art" ) mon_image_gallery = gr.Gallery(label = "Generated Images", show_label = True, elem_id = "gallery", columns =[4], rows =[1], object_fit = "cover", height ="auto") with gr.Column(): token_gen = gr.Button(value = "Generate Token Image" ) mon_token_gallery = gr.Gallery(label = "Generated Tokens", show_label = True, elem_id = "token_gallery", columns =[4], rows =[1], object_fit = "cover", height ="auto") model_gen_instructions = """## Generate a 3d model 1. Make sure an image was clicked in the "Generated Images" gallery. *Works best with images without thin qualities, like antenna \n 2. Click Generate a 3d model button. 3. Wait about 30 seconds, then review and download as an .glb file. """ gr.Markdown(model_gen_instructions) model_gen = gr.Button(value= "Generate a 3D model") mon_model_gallery = gr.Model3D(label = "3D Model Display", elem_id = "model_gallery", ) image_gen.click(generate_image_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_image_gallery) token_gen.click(generate_token_update_gallery, inputs = [mon_sd_prompt_output,mon_name], outputs = mon_token_gallery) model_gen.click(generate_model_update_gallery, inputs = selected_generated_image, outputs = mon_model_gallery) mon_image_gallery.select(fn = assign_img_path, outputs=selected_generated_image) mon_token_gallery.select(fn = assign_img_path, outputs=selected_token_image) desc_gen.click(fn = gen_mon_desc, inputs = [user_mon_description,spells_checkbox, legendary_action_checkbox], outputs= [mon_name, mon_name_output, mon_size,mon_size_output, mon_type,mon_type_output, mon_subtype,mon_subtype_output, mon_alignment, mon_alignment_output, mon_armor_class, mon_armor_class_output, mon_hp, mon_hp_output, mon_hit_dice, mon_hit_dice_output, mon_speed, mon_speed_output, mon_abilities, mon_abilities_output, mon_saving_throws, mon_saving_throws_output, mon_skills, mon_skills_output, mon_damage_resistance, mon_damage_resistance_output, mon_senses, mon_senses_output, mon_languages, mon_languages_output, mon_challenge_rating, mon_challenge_rating_output, mon_xp, mon_xp_output, mon_actions, mon_actions_output, mon_cantrips,mon_cantrips_output, mon_spells, mon_spells_output, mon_spell_slots, mon_spell_slot_output, mon_legendary_actions, mon_legendary_actions_output, mon_description, mon_description_output, mon_sd_prompt,mon_sd_prompt_output ]) # Build buttons to modify to html and show html gen_html = gr.Button(value = "Step 3 : Generate html") html = gr.HTML(label="HTML preview", show_label=True) gen_html.click(build_html_file,inputs =[ mon_name_output, mon_size_output, mon_type_output, mon_subtype_output, mon_alignment_output, mon_armor_class_output, mon_hp_output, mon_hit_dice_output, mon_speed_output, mon_abilities_output, mon_saving_throws_output, mon_skills_output, mon_damage_resistance_output, mon_senses_output, mon_languages_output, mon_challenge_rating_output, mon_xp_output, mon_actions_output, mon_description_output, selected_generated_image, mon_cantrips_output, mon_spells_output, mon_spell_slot_output, mon_legendary_actions_output, ], outputs= html ) example_headers = ['## Statblock Examples', '## Token Examples', '## 3D Model Examples'] with gr.Tab("Statblock Examples"): gr.Markdown(example_headers[0]) examples = u.absolute_path("./galleries/examples/statblocks") example_gallery = gr.Gallery(label = "Statblock Examples", show_label = True, elem_id = "gallery", columns =[4], rows =[1], object_fit = "fill", height = 768, value = examples) with gr.Tab("Token Examples"): example_headers = ['## Statblock Examples', '## Token Examples', '## 3D Model Examples'] gr.Markdown(example_headers[1]) examples = u.absolute_path("./galleries/examples/tokens") example_gallery = gr.Gallery(label = "Token Examples", show_label = True, elem_id = "gallery", columns =[4], rows =[1], object_fit = "fill", height = 768, value = examples) with gr.Tab("3d Model Examples"): example_headers = ['## Statblock Examples', '## Token Examples', '## 3D Model Examples'] gr.Markdown(example_headers[2]) examples = u.absolute_path("./galleries/examples/models") with gr.Row(): example_gallery = gr.Model3D(label = "3d Model Examples", show_label = True, value = examples[0]) example_gallery = gr.Model3D(label = "3d Model Examples", show_label = True, value = examples[1]) if __name__ == "__main__": demo.launch(allowed_paths=list_of_static_dir)