feat(IconExporter): blocks/items 폴더 구분 및 ZIP 압축 기능 추가
- BlockItem 여부로 blocks/items 폴더 자동 분류 - 추출 완료 후 icons_<mod>_<timestamp>.zip 자동 생성 - README 업데이트
This commit is contained in:
parent
bf8f980fed
commit
27888c557d
2 changed files with 98 additions and 19 deletions
|
|
@ -48,19 +48,27 @@
|
||||||
|
|
||||||
## 📁 출력 구조
|
## 📁 출력 구조
|
||||||
|
|
||||||
추출된 아이콘은 `.minecraft/icons/` 폴더에 저장됩니다.
|
아이콘은 `.minecraft/icons/` 폴더에 **blocks/items**로 구분되어 저장됩니다.
|
||||||
|
완료 후 자동으로 ZIP 파일이 생성됩니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
.minecraft/
|
.minecraft/
|
||||||
└── icons/
|
├── icons/
|
||||||
├── minecraft/
|
│ ├── minecraft/
|
||||||
│ ├── diamond.png
|
│ │ ├── blocks/
|
||||||
│ ├── iron_ingot.png
|
│ │ │ ├── stone.png
|
||||||
│ └── ...
|
│ │ │ └── ...
|
||||||
└── create/
|
│ │ └── items/
|
||||||
├── mechanical_press.png
|
│ │ ├── diamond.png
|
||||||
├── brass_ingot.png
|
│ │ └── ...
|
||||||
└── ...
|
│ └── create/
|
||||||
|
│ ├── blocks/
|
||||||
|
│ │ ├── brass_casing.png
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── items/
|
||||||
|
│ ├── brass_ingot.png
|
||||||
|
│ └── ...
|
||||||
|
└── icons_create_20251226_181500.zip ← 자동 생성
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,27 @@ package com.beemer.iconexporter.export
|
||||||
|
|
||||||
import com.beemer.iconexporter.IconExporter
|
import com.beemer.iconexporter.IconExporter
|
||||||
import com.beemer.iconexporter.render.IconRenderer
|
import com.beemer.iconexporter.render.IconRenderer
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.name
|
||||||
|
import kotlin.io.path.pathString
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.core.registries.BuiltInRegistries
|
import net.minecraft.core.registries.BuiltInRegistries
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
|
import net.minecraft.world.item.BlockItem
|
||||||
import net.minecraft.world.item.Item
|
import net.minecraft.world.item.Item
|
||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 아이콘 추출 관리자
|
* 아이콘 추출 관리자
|
||||||
*
|
*
|
||||||
* 대량의 아이템을 처리할 때 게임 프리즈를 방지하기 위해 틱 기반 큐 시스템으로 아이템을 순차적으로 처리합니다.
|
* 대량의 아이템을 처리할 때 게임 프리즈를 방지하기 위해 틱 기반 큐 시스템으로 아이템을 순차적으로 처리합니다. 완료 후 icons 폴더를 ZIP으로 압축합니다.
|
||||||
*/
|
*/
|
||||||
object IconExportManager {
|
object IconExportManager {
|
||||||
// 틱당 처리할 아이템 수 (너무 높으면 랙, 너무 낮으면 느림)
|
// 틱당 처리할 아이템 수 (너무 높으면 랙, 너무 낮으면 느림)
|
||||||
|
|
@ -127,8 +134,12 @@ object IconExportManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 아이템이 블록인지 확인 (BlockItem이면 블록, 아니면 아이템)
|
||||||
|
val isBlock = item is BlockItem
|
||||||
|
val type = if (isBlock) "blocks" else "items"
|
||||||
|
|
||||||
// 저장 경로 생성
|
// 저장 경로 생성
|
||||||
val outputPath = getOutputPath(itemId.namespace, itemId.path)
|
val outputPath = getOutputPath(itemId.namespace, type, itemId.path)
|
||||||
|
|
||||||
// 이미 파일이 존재하고 덮어쓰기가 비활성화된 경우 스킵
|
// 이미 파일이 존재하고 덮어쓰기가 비활성화된 경우 스킵
|
||||||
if (outputPath.exists() && !overwrite) {
|
if (outputPath.exists() && !overwrite) {
|
||||||
|
|
@ -162,13 +173,16 @@ object IconExportManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 아이템 ID로 출력 경로 생성 */
|
/**
|
||||||
private fun getOutputPath(namespace: String, path: String): java.nio.file.Path {
|
* 아이템 ID로 출력 경로 생성 형식: icons/<namespace>/<type>/<path>.png 예:
|
||||||
|
* icons/create/blocks/brass_casing.png
|
||||||
|
*/
|
||||||
|
private fun getOutputPath(namespace: String, type: String, path: String): Path {
|
||||||
val mc = Minecraft.getInstance()
|
val mc = Minecraft.getInstance()
|
||||||
val gameDir = mc.gameDirectory.toPath()
|
val gameDir = mc.gameDirectory.toPath()
|
||||||
|
|
||||||
// icons/<namespace>/<path>.png
|
// icons/<namespace>/<type>/<path>.png
|
||||||
return gameDir.resolve("icons").resolve(namespace).resolve("$path.png")
|
return gameDir.resolve("icons").resolve(namespace).resolve(type).resolve("$path.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 진행률 로그 출력 */
|
/** 진행률 로그 출력 */
|
||||||
|
|
@ -199,6 +213,17 @@ object IconExportManager {
|
||||||
isRunning = false
|
isRunning = false
|
||||||
val elapsed = System.currentTimeMillis() - startTime
|
val elapsed = System.currentTimeMillis() - startTime
|
||||||
|
|
||||||
|
val mc = Minecraft.getInstance()
|
||||||
|
val iconsDir = mc.gameDirectory.toPath().resolve("icons")
|
||||||
|
|
||||||
|
// ZIP 압축 생성
|
||||||
|
var zipPath: Path? = null
|
||||||
|
try {
|
||||||
|
zipPath = createZipArchive(iconsDir)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
IconExporter.LOGGER.error("ZIP 압축 실패: {}", e.message)
|
||||||
|
}
|
||||||
|
|
||||||
val summary =
|
val summary =
|
||||||
"""
|
"""
|
||||||
§a=== 추출 완료 ===
|
§a=== 추출 완료 ===
|
||||||
|
|
@ -221,17 +246,63 @@ object IconExportManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 게임 내 메시지
|
// 게임 내 메시지
|
||||||
val mc = Minecraft.getInstance()
|
|
||||||
mc.player?.let { player ->
|
mc.player?.let { player ->
|
||||||
player.displayClientMessage(Component.literal(summary), false)
|
player.displayClientMessage(Component.literal(summary), false)
|
||||||
|
|
||||||
// 저장 위치 안내
|
// ZIP 경로 안내
|
||||||
val outputDir = mc.gameDirectory.toPath().resolve("icons")
|
if (zipPath != null) {
|
||||||
player.displayClientMessage(
|
player.displayClientMessage(
|
||||||
Component.literal("§7저장 위치: §f${outputDir.toAbsolutePath()}"),
|
Component.literal("§7ZIP 파일: §f${zipPath.toAbsolutePath()}"),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 저장 위치 안내
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.literal("§7아이콘 폴더: §f${iconsDir.toAbsolutePath()}"),
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** icons 폴더를 ZIP으로 압축 파일명: icons_<modFilter>_<timestamp>.zip */
|
||||||
|
private fun createZipArchive(iconsDir: Path): Path? {
|
||||||
|
if (!iconsDir.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val mc = Minecraft.getInstance()
|
||||||
|
val gameDir = mc.gameDirectory.toPath()
|
||||||
|
|
||||||
|
// ZIP 파일명 생성
|
||||||
|
val timestamp =
|
||||||
|
java.time.LocalDateTime.now()
|
||||||
|
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))
|
||||||
|
val modName = currentModFilter ?: "all"
|
||||||
|
val zipFileName = "icons_${modName}_$timestamp.zip"
|
||||||
|
val zipPath = gameDir.resolve(zipFileName)
|
||||||
|
|
||||||
|
IconExporter.LOGGER.info("ZIP 압축 생성 중: {}", zipPath)
|
||||||
|
|
||||||
|
ZipOutputStream(FileOutputStream(zipPath.toFile())).use { zos ->
|
||||||
|
Files.walk(iconsDir).use { paths ->
|
||||||
|
paths.filter { Files.isRegularFile(it) && it.name.endsWith(".png") }.forEach { file
|
||||||
|
->
|
||||||
|
try {
|
||||||
|
// ZIP 내 상대 경로: <namespace>/<type>/<item>.png
|
||||||
|
val relativePath = iconsDir.relativize(file).pathString.replace("\\", "/")
|
||||||
|
zos.putNextEntry(ZipEntry(relativePath))
|
||||||
|
Files.copy(file, zos)
|
||||||
|
zos.closeEntry()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
IconExporter.LOGGER.error("ZIP에 파일 추가 실패: {} - {}", file, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconExporter.LOGGER.info("ZIP 압축 완료: {}", zipPath)
|
||||||
|
return zipPath
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 작업 취소 */
|
/** 작업 취소 */
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue