Add build-local render and release workflows

This commit is contained in:
2026-08-10 21:54:05 +01:00
parent eadef6c60e
commit 4a91533b63
244 changed files with 3585 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
plugins {
id 'fabric-loom' version '1.7-SNAPSHOT'
id 'java'
}
group = 'dev.github.copilot'
version = '1.0.0'
repositories {
maven { url = 'https://maven.fabricmc.net/' }
mavenCentral()
}
def minecraftVersion = providers.environmentVariable('MINECRAFT_VERSION')
.orElse(providers.gradleProperty('minecraft_version'))
.get()
dependencies {
minecraft "com.mojang:minecraft:${minecraftVersion}"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:0.16.10"
implementation 'com.google.code.gson:gson:2.11.0'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.release = 21
}
sourceSets {
main {
resources.srcDir(file('../resourcepack'))
}
}
loom {
runs {
client {
programArgs '--username', 'RenderBot'
}
}
}
+1
View File
@@ -0,0 +1 @@
rootProject.name = 'renameable-item-renderer'
@@ -0,0 +1,216 @@
package dev.github.copilot.renameable;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.ScreenshotRecorder;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
public final class RenderCatalogClient implements ClientModInitializer {
private static final Gson GSON = new Gson();
private final List<RenderJob> jobs = new ArrayList<>();
private Path outputDir;
private int currentJobIndex;
@Override
public void onInitializeClient() {
Path manifestPath = readPathEnv("RENDER_MANIFEST");
outputDir = readPathEnv("RENDER_OUTPUT_DIR");
if (manifestPath == null || outputDir == null) {
return;
}
try {
loadJobs(manifestPath);
} catch (IOException exception) {
throw new RuntimeException("Failed to load render manifest", exception);
}
jobs.sort(Comparator.comparing(RenderJob::outputPath));
if (jobs.isEmpty()) {
return;
}
MinecraftClient client = MinecraftClient.getInstance();
client.execute(() -> client.setScreen(new RenderScreen()));
}
private void loadJobs(Path manifestPath) throws IOException {
try (Reader reader = Files.newBufferedReader(manifestPath, StandardCharsets.UTF_8)) {
JsonElement root = JsonParser.parseReader(reader);
if (!root.isJsonArray()) {
return;
}
JsonArray records = root.getAsJsonArray();
for (JsonElement recordElement : records) {
if (!recordElement.isJsonObject()) {
continue;
}
JsonObject record = recordElement.getAsJsonObject();
collectJob(record.get("item"), record.get("render_path"), null);
JsonElement casesElement = record.get("cases");
if (!casesElement.isJsonArray()) {
continue;
}
for (JsonElement caseElement : casesElement.getAsJsonArray()) {
if (!caseElement.isJsonObject()) {
continue;
}
JsonObject caseRecord = caseElement.getAsJsonObject();
collectJob(record.get("item"), caseRecord.get("render_path"), caseRecord.get("when"));
}
}
}
}
private void collectJob(JsonElement itemElement, JsonElement renderPathElement, JsonElement customNameElement) {
if (itemElement == null || !itemElement.isJsonPrimitive()) {
return;
}
if (renderPathElement == null || renderPathElement.isJsonNull() || !renderPathElement.isJsonPrimitive()) {
return;
}
String itemId = itemElement.getAsString();
String renderPath = renderPathElement.getAsString();
String customName = customNameElement != null && customNameElement.isJsonPrimitive() ? customNameElement.getAsString() : null;
ItemStack stack = new ItemStack(Registries.ITEM.get(Identifier.of("minecraft", itemId)));
if (stack.isEmpty()) {
return;
}
if (customName != null) {
stack.setCustomName(Text.literal(customName));
}
jobs.add(new RenderJob(renderPath, stack));
}
private Path readPathEnv(String key) {
String value = System.getenv(key);
if (value == null || value.isBlank()) {
return null;
}
return Path.of(value);
}
private void renderCurrentJob(MinecraftClient client, RenderScreen screen) {
if (currentJobIndex >= jobs.size()) {
client.scheduleStop();
return;
}
RenderJob job = jobs.get(currentJobIndex);
Path jobPath = outputDir.resolve(job.outputPath()).normalize();
Path parent = jobPath.getParent();
if (parent == null) {
client.scheduleStop();
return;
}
try {
Files.createDirectories(parent);
} catch (IOException exception) {
throw new RuntimeException("Failed to create render output directory", exception);
}
String fileName = stripPngSuffix(jobPath.getFileName().toString());
ScreenshotRecorder.saveScreenshot(parent.toFile(), fileName, client.getFramebuffer(), client.getWindow().getFramebufferWidth(), status -> client.execute(this::advanceJob));
screen.markCaptureQueued();
}
private void advanceJob() {
currentJobIndex++;
MinecraftClient client = MinecraftClient.getInstance();
if (currentJobIndex >= jobs.size()) {
client.scheduleStop();
return;
}
Screen currentScreen = client.currentScreen;
if (currentScreen instanceof RenderScreen renderScreen) {
renderScreen.resetCaptureState();
}
}
private String stripPngSuffix(String fileName) {
if (fileName.endsWith(".png")) {
return fileName.substring(0, fileName.length() - 4);
}
return fileName;
}
private record RenderJob(String outputPath, ItemStack stack) {
}
private final class RenderScreen extends Screen {
private boolean captureQueued;
private RenderScreen() {
super(Text.literal("Rendering renameable items"));
}
private void markCaptureQueued() {
captureQueued = true;
}
private void resetCaptureState() {
captureQueued = false;
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.fill(0, 0, width, height, 0x00000000);
if (currentJobIndex < jobs.size()) {
RenderJob job = jobs.get(currentJobIndex);
context.getMatrices().push();
context.getMatrices().translate(width / 2.0, height / 2.0, 0.0);
context.getMatrices().scale(8.0f, 8.0f, 1.0f);
context.drawItem((LivingEntity) null, job.stack(), -1, -1, 0);
context.getMatrices().pop();
if (!captureQueued) {
captureQueued = true;
MinecraftClient client = MinecraftClient.getInstance();
client.execute(() -> renderCurrentJob(client, this));
}
}
}
@Override
public boolean shouldPause() {
return false;
}
}
}
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"id": "renameable-item-renderer",
"version": "${version}",
"name": "Renameable Item Renderer",
"description": "Renders renameable item previews with the Minecraft client renderer.",
"environment": "client",
"entrypoints": {
"client": [
"dev.github.copilot.renameable.RenderCatalogClient"
]
},
"depends": {
"minecraft": "*",
"fabricloader": ">=0.16.10"
}
}