Skip to content

3.5 Implementing the Project Generator

Zhoumy303 edited this page Sep 27, 2026 · 1 revision

The goal of this task is to generate a complete Fabric project directory based on a validated blueprint, including base template rendering and the generation framework for item-related files.
Task 2 already completed template parameterization and the render_project() function. This task builds on that by integrating the item list from the blueprint, calling the subsequent Java code generator and resource generator (Tasks 6 and 7), and finally outputting a compilable project.


Prerequisites

  • Completed Task 3: modsmith/templates/fabric-project/ template is parameterized and render_project() is available.
  • Completed Task 4: generate_validated_blueprint() returns valid blueprints.
  • Familiar with the blueprint structure, in which the items array contains multiple item definitions.

Step 1: Understand the responsibilities of the project generator

The project generator generate_project(blueprint, output_dir) needs to do the following:

  1. Render the base project template: call render_project() to generate build.gradle, gradle.properties, fabric.mod.json, the main entrypoint class, etc.
  2. Generate item registration code: generate the ID class, registration class, and initialization code for each item (Task 6).
  3. Generate resource files: generate model JSON, translation files, and client item definitions for each item (Task 7).
  4. Generate textures: generate placeholder textures for each item (Task 8, optional).
  5. Ensure the project structure is complete: all file paths are correct and the project compiles.

Task 5 mainly implements Step 1 and Step 5, and reserves interfaces for calling Tasks 6, 7, and 8.


Step 2: Create modsmith/generator/java.py (skeleton for Task 6)

To let the project generator produce item registration code, we first create the base framework of the Java code generator. The full implementation will be refined in Task 6; here we provide a working version first.

Create modsmith/generator/java.py:

"""Generate Java code related to item registration."""

from pathlib import Path
from jinja2 import Environment, FileSystemLoader

TEMPLATE_DIR = Path(__file__).parent.parent / "templates" / "java"


def generate_java_items(blueprint: dict, project_dir: Path) -> None:
    """Generate Java code for the items in the blueprint.

    Args:
        blueprint: Blueprint dictionary.
        project_dir: Root directory of the generated project.
    """
    env = Environment(
        loader=FileSystemLoader(str(TEMPLATE_DIR)),
        keep_trailing_newline=True,
    )

    # Prepare package path
    package_path = blueprint["package_name"].replace(".", "/")
    java_dir = project_dir / "src" / "main" / "java" / package_path
    java_dir.mkdir(parents=True, exist_ok=True)

    # Render ModItemIds.java
    template = env.get_template("ModItemIds.java.jinja")
    content = template.render(
        package_name=blueprint["package_name"],
        items=blueprint["items"],
    )
    (java_dir / "ModItemIds.java").write_text(content, encoding="utf-8")

    # Render ModItems.java
    template = env.get_template("ModItems.java.jinja")
    content = template.render(
        package_name=blueprint["package_name"],
        items=blueprint["items"],
    )
    (java_dir / "ModItems.java").write_text(content, encoding="utf-8")

    # Render ModItemsGenerated.java (for initialization)
    template = env.get_template("ModItemsGenerated.java.jinja")
    content = template.render(
        package_name=blueprint["package_name"],
        items=blueprint["items"],
    )
    (java_dir / "ModItemsGenerated.java").write_text(content, encoding="utf-8")

Note: The corresponding Jinja templates need to be created under modsmith/templates/java/. Since Task 6 will explain this in detail, a simplified version is given here so the project generator can run.

To get this task working, we first create the minimal versions of these templates:

modsmith/templates/java/ModItemIds.java.jinja

package {{ package_name }};

import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.Item;

public class ModItemIds {
    {% for item in items %}
    public static final ResourceKey<Item> {{ item.id | upper }} = create("{{ item.id }}");
    {% endfor %}

    private static ResourceKey<Item> create(String name) {
        return ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath("{{ mod_id }}", name));
    }
}

Note that mod_id needs to be passed in. Add mod_id to the render context in generate_java_items.

modsmith/templates/java/ModItems.java.jinja

package {{ package_name }};

import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;

public class ModItems {
    {% for item in items %}
    public static final Item {{ item.id | upper }} = register(ModItemIds.{{ item.id | upper }}, Item::new, new Item.Properties());
    {% endfor %}

    private static Item register(ResourceKey<Item> key, Function<Item.Properties, Item> factory, Item.Properties settings) {
        Item item = factory.apply(settings.setId(key));
        return Registry.register(BuiltInRegistries.ITEM, key, item);
    }

    public static void initialize() {
        // Empty initialization to trigger class loading
    }
}

modsmith/templates/java/ModItemsGenerated.java.jinja

package {{ package_name }};

public class ModItemsGenerated {
    public static void initialize() {
        ModItems.initialize();
    }
}

These templates are only skeletons. Task 6 will generate more complete code (supporting food, fuel, tool, etc.).


Step 3: Create modsmith/generator/resources.py (skeleton for Task 7)

Create the base framework of the resource file generator.

Create modsmith/generator/resources.py:

"""Generate item-related resource files (models, translations, client item definitions)."""

import json
from pathlib import Path


def generate_resources(blueprint: dict, project_dir: Path) -> None:
    """Generate resource files for the items in the blueprint.

    Args:
        blueprint: Blueprint dictionary.
        project_dir: Root directory of the generated project.
    """
    mod_id = blueprint["mod_id"]
    assets_dir = project_dir / "src" / "main" / "resources" / "assets" / mod_id

    # Create directories
    (assets_dir / "items").mkdir(parents=True, exist_ok=True)
    (assets_dir / "models" / "item").mkdir(parents=True, exist_ok=True)
    (assets_dir / "textures" / "item").mkdir(parents=True, exist_ok=True)
    (assets_dir / "lang").mkdir(parents=True, exist_ok=True)

    # Collect translation key-value pairs
    en_us = {}
    zh_cn = {}

    for item in blueprint["items"]:
        item_id = item["id"]

        # Client item definition
        client_item = {
            "model": {
                "type": "minecraft:model",
                "model": f"{mod_id}:item/{item_id}"
            }
        }
        (assets_dir / "items" / f"{item_id}.json").write_text(
            json.dumps(client_item, indent=2, ensure_ascii=False), encoding="utf-8"
        )

        # Model file
        model = {
            "parent": "minecraft:item/generated",
            "textures": {
                "layer0": f"{mod_id}:item/{item_id}"
            }
        }
        (assets_dir / "models" / "item" / f"{item_id}.json").write_text(
            json.dumps(model, indent=2, ensure_ascii=False), encoding="utf-8"
        )

        # Translation keys
        en_us[f"item.{mod_id}.{item_id}"] = item.get("display_name_en", item_id)
        if "display_name_zh" in item:
            zh_cn[f"item.{mod_id}.{item_id}"] = item["display_name_zh"]

    # Write translation files
    (assets_dir / "lang" / "en_us.json").write_text(
        json.dumps(en_us, indent=2, ensure_ascii=False), encoding="utf-8"
    )
    if zh_cn:
        (assets_dir / "lang" / "zh_cn.json").write_text(
            json.dumps(zh_cn, indent=2, ensure_ascii=False), encoding="utf-8"
        )

Note: This version only generates generic models and translations for the basic type. Task 7 will extend it to support models and additional files for different types such as food, fuel, and tool.


Step 4: Enhance the generate_project function in modsmith/generator/project.py

In Task 2, render_project() was only responsible for rendering templates. Now we need a higher-level function generate_project() that first calls render_project() to generate the base project, then calls generate_java_items() and generate_resources() to add item content.

Open modsmith/generator/project.py and add at the end of the file:

from modsmith.generator.java import generate_java_items
from modsmith.generator.resources import generate_resources


def generate_project(blueprint: dict, output_dir: Path) -> None:
    """Generate a complete Fabric project based on the blueprint.

    Args:
        blueprint: A validated blueprint dictionary.
        output_dir: Output project directory.
    """
    # 1. Render the base project template
    render_project(blueprint, output_dir)

    # 2. Generate item Java code
    generate_java_items(blueprint, output_dir)

    # 3. Generate resource files
    generate_resources(blueprint, output_dir)

    print(f"✅ Project fully generated at: {output_dir}")

Note: generate_java_items needs to use mod_id, so we supplement the context when rendering. Modify the render context in generate_java_items:

    context = {
        "package_name": blueprint["package_name"],
        "items": blueprint["items"],
        "mod_id": blueprint["mod_id"],
    }

And use {{ mod_id }} in the template.

Also, ModItems.java.jinja needs to import Function and ResourceKey, so we need to complete it:

import java.util.function.Function;
import net.minecraft.resources.ResourceKey;

To simplify, we can complete the template in Task 6. Here we just make sure it can be generated.


Step 5: Create the test script test_generator.py

"""Test the project generator."""

from pathlib import Path

from modsmith.blueprint.validator import generate_validated_blueprint
from modsmith.generator.project import generate_project

# Generate blueprint
blueprint = generate_validated_blueprint("Create an apple that restores 4 hunger points when eaten")

# Generate project
output_dir = Path("./generated_project")
generate_project(blueprint, output_dir)

Step 6: Run the verification

python test_generator.py

Expected result:

  • Generates the generated_project/ directory.
  • The directory contains a complete Fabric project structure:
    • build.gradle, gradle.properties, gradlew, etc.
    • Under src/main/java/com/example/: ExampleMod.java, ModItemIds.java, ModItems.java, ModItemsGenerated.java.
    • Under src/main/resources/assets/example-mod/: models, translations, client item definitions.
  • Entering generated_project/ and running ./gradlew build should compile (may need to refine the Java template according to Task 6).

Step 7: Common issues and solutions

Problem Cause Solution
ModuleNotFoundError: No module named 'modsmith.generator.java' File not created or wrong path Confirm java.py and resources.py are created under modsmith/generator/
Generated Java code fails to compile Template missing import or field Task 6 will refine the templates; for now, comment out the ModItemsGenerated call
Translation file not generated display_name_zh missing Check whether the blueprint contains a Chinese name, or modify resources.py to allow empty Chinese
gradlew has no execute permission Permission not set Task 2's render_project handles this; confirm the output directory's gradlew permission is 755

Step 8: Task 5 Acceptance Criteria

  • modsmith/generator/java.py is created and can generate the basic skeletons of ModItemIds.java, ModItems.java, and ModItemsGenerated.java.
  • modsmith/generator/resources.py is created and can generate client item definitions, models, and translation files for each item.
  • generate_project() in modsmith/generator/project.py integrates template rendering, Java code generation, and resource generation.
  • test_generator.py runs end to end and generates a project directory containing all required files.
  • The generated project structure is complete and ready for the next step of refining Java code (Task 6) and resource details (Task 7).

After completing this task, your ModSmith will have the ability to generate a complete project directory from a blueprint. Next, Task 6 will refine the Java code generator (supporting food, fuel, tool), and Task 7 will refine resource generation (supporting different models and translations for different types).

Clone this wiki locally