diff --git a/commands/createAtlas.js b/commands/createAtlas.js index cd68305e..5c2cf59f 100644 --- a/commands/createAtlas.js +++ b/commands/createAtlas.js @@ -1,172 +1,172 @@ const vscode = require('vscode'); -const { exec } = require('child_process'); const path = require('path'); -const { v4: uuidv4 } = require('uuid'); const fs = require('fs'); -const { getConfig } = require('../support_files/config'); +const { Magick, MagickCore } = require('magickwand.js'); +const xmlbuilder = require('xmlbuilder'); +const { getConfig, setConfig } = require('../support_files/config'); const { getModName } = require('../support_files/helper_functions.js'); +const { v4: uuidv4 } = require('uuid'); -function findDivineExe(lslibPath) { - let divinePath = null; - - function searchDir(dir) { - const files = fs.readdirSync(dir); - for (const file of files) { - const filePath = path.join(dir, file); - const stat = fs.lstatSync(filePath); - if (stat.isDirectory()) { - searchDir(filePath); - } else if (file === 'Divine.exe') { - divinePath = filePath; - return; - } - } - } - - if (fs.existsSync(lslibPath) && fs.lstatSync(lslibPath).isDirectory()) { - searchDir(lslibPath); - } else { - vscode.window.showErrorMessage('lslib directory not found.'); - throw new Error('lslib directory not found.'); - } - - if (!divinePath) { - vscode.window.showErrorMessage('No divine.exe found in lslib directory.'); - throw new Error('No divine.exe found in lslib directory.'); - } - - return divinePath; +const truncate = (number, digits) => { + const stepper = Math.pow(10.0, digits); + return Math.round(number * stepper) / stepper; } +async function createAtlas(iconsDir, atlasPath, texturePath, textureUUID) { + const { rootModPath } = getConfig(); + const modName = await getModName(); + const iconSize = 64; + const textureSize = 2048; + const textureSizeString = `${textureSize}x${textureSize}`; -let createAtlasCommand = vscode.commands.registerCommand('bg3-mod-helper.createAtlas', async function () { // Made the function async - console.log('‾‾createAtlasCommand‾‾'); - const { rootModPath, lslibPath } = getConfig(); + let atlas = new Magick.Image(textureSizeString, 'rgba(0, 0, 0, 0)'); - const scriptPath = path.join(__dirname, '..', 'support_files', 'scripts', 'python', 'add_icons_to_atlas.py'); + const icons = await fs.promises.readdir(iconsDir); + const iconXMLNodes = []; - let import_test = false + for (let i = 0; i < icons.length; i++) { + const iconPath = path.join(iconsDir, icons[i]); + const iconName = path.parse(icons[i]).name; + const iconImage = new Magick.Image(); + await iconImage.readAsync(iconPath); - let divinePath_; - try { - divinePath_ = findDivineExe(lslibPath); - } catch (error) { - console.log(error) - return; + const x = (i % (textureSize / iconSize)) * iconSize; + const y = Math.floor(i / (textureSize / iconSize)) * iconSize; + + const u1 = truncate(x / textureSize, 7); + const v1 = truncate(y / textureSize, 7); + const u2 = truncate((x + iconSize) / textureSize, 7); + const v2 = truncate((y + iconSize) / textureSize, 7); + + const geometry = new Magick.Geometry(iconSize, iconSize, x, y); + await atlas.compositeAsync(iconImage, geometry, MagickCore.OverCompositeOp); + + iconXMLNodes.push({ + '@id': 'IconUV', + attribute: [ + { '@id': 'MapKey', '@value': iconName, '@type': 'FixedString' }, + { '@id': 'U1', '@value': u1.toString(), '@type': 'float' }, + { '@id': 'V1', '@value': v1.toString(), '@type': 'float' }, + { '@id': 'U2', '@value': u2.toString(), '@type': 'float' }, + { '@id': 'V2', '@value': v2.toString(), '@type': 'float' } + ] + }); } - const modName = await getModName(); - - function checkPythonImports(packages) { - return new Promise((resolve, reject) => { - let importCommands = packages.map(pkg => `import ${pkg}`).join(';'); - exec(`python -c "${importCommands}"`, (error, stdout, stderr) => { - if (error) { - reject(`Missing package(s): ${packages.join(', ')}`); - } else { - resolve(); + const ddsTexturePath = texturePath.replace('.png', '.dds'); + await atlas.writeAsync(ddsTexturePath); + + const relativeTexturePath = modName + path.sep + path.relative(path.join(rootModPath, 'Public', modName), ddsTexturePath); + + const xmlContent = xmlbuilder.create({ + save: { + version: { + '@major': '4', '@minor': '0', '@revision': '9', '@build': '322' + }, + region: [ + { + '@id': 'IconUVList', + node: { + '@id': 'root', + children: { + node: iconXMLNodes + } + } + }, + { + '@id': 'TextureAtlasInfo', + node: { + '@id': 'root', + children: { + node: [ + { + '@id': 'TextureAtlasIconSize', + attribute: [ + { '@id': 'Height', '@value': iconSize, '@type': 'int32' }, + { '@id': 'Width', '@value': iconSize, '@type': 'int32' } + ] + }, + { + '@id': 'TextureAtlasPath', + attribute: [ + { '@id': 'Path', '@value': relativeTexturePath, '@type': 'string' }, + { '@id': 'UUID', '@value': textureUUID, '@type': 'FixedString' } + ] + }, + { + '@id': 'TextureAtlasTextureSize', + attribute: [ + { '@id': 'Height', '@value': textureSize, '@type': 'int32' }, + { '@id': 'Width', '@value': textureSize, '@type': 'int32' } + ] + } + ] + } + } } - }); - }); - } + ] + } + }, { encoding: 'UTF-8' }).end({ pretty: true }); - try { - await checkPythonImports(['PIL', 'numpy']); - import_test = true; - } catch (error) { - const terminal = vscode.window.createTerminal('Package Install'); - terminal.show(); - terminal.sendText('pip install Pillow numpy', false); - vscode.window.showInformationMessage(`${error}. Please run the command in the opened terminal by pressing Enter.`); + fs.writeFileSync(atlasPath, xmlContent); +} + +let createAtlasCommand = vscode.commands.registerCommand('bg3-mod-helper.createAtlas', async function () { + console.log('‾‾createAtlasCommand‾‾'); + const { rootModPath } = getConfig(); + const modName = await getModName(); // Assuming this function correctly fetches the mod's name + const newUuid = uuidv4(); + + // Directories for icons, texture, and atlas XML + const iconsDirPath = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false }); + if (!iconsDirPath) { + vscode.window.showErrorMessage('Icons directory not selected.'); return; } - if (import_test === true) { - const newUuid = uuidv4(); - // Function to prompt user to select a directory - async function selectDirectory() { - const options = { - canSelectMany: false, - openLabel: 'Select', - canSelectFolders: true, - canSelectFiles: false - }; - const fileUri = await vscode.window.showOpenDialog(options); - if (fileUri && fileUri[0]) { - return fileUri[0].fsPath; - } else { - return null; - } - } + const texturesDirPath = path.join(rootModPath, 'Public', modName, 'Assets', 'Textures', 'Icons'); + const texturePath = path.join(texturesDirPath, `Icons_${modName}.png`); + const atlasDirPath = path.join(rootModPath, 'Public', modName, 'GUI'); + const atlasPath = path.join(atlasDirPath, `Icons_${modName}.lsx`); - // Prompt user to select the icons directory - const iconsDirPath = await selectDirectory(); - if (!iconsDirPath) { - vscode.window.showErrorMessage('Icons directory not selected.'); - return; - } + const mergedDirPath = path.join(rootModPath, 'Public', modName, 'Content', 'UI', '[PAK]_UI'); + const mergedPath = path.join(mergedDirPath, 'merged.lsx'); - const texturesDirPath = path.join(rootModPath, 'Public', modName, 'Assets', 'Textures', 'Icons'); - const texturesPath = path.join(texturesDirPath, `Icons_${modName}.dds`); - const atlasDirPath = path.join(rootModPath, 'Public', modName, 'GUI'); - const atlasPath = path.join(atlasDirPath, `Icons_${modName}.lsx`); - const mergedDirPath = path.join(rootModPath, 'Public', modName, 'Content', 'UI', '[PAK]_UI'); - const mergedPath = path.join(mergedDirPath, 'merged.lsx'); - const args = [ - '-i', `"${iconsDirPath}"`, - '-a', `"${atlasPath}"`, - '-t', `"${texturesPath}"`, - '-u', `"${newUuid}"`, - '--divine', `"${divinePath_}"` - ].join(' '); - - // Before writing the modified content to the new merged.lsx file - if (!fs.existsSync(mergedDirPath)) { - fs.mkdirSync(mergedDirPath, { recursive: true }); + // Ensure directories exist + [texturesDirPath, atlasDirPath].forEach(dir => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); } + }); - const command = `python "${scriptPath}" ${args}`; - - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(`Error: ${error.message}`); - vscode.window.showErrorMessage(`Error: ${error.message}`); - return; - } - if (stderr) { - console.error(`stderr: ${stderr}`); - vscode.window.showErrorMessage(`stderr: ${stderr}`); - return; - } - console.log(`stdout: ${stdout}`); - vscode.window.showInformationMessage('Python script executed successfully.'); - }); - console.log('#################################') - // Check if merged.lsx exists - if (fs.existsSync(mergedPath)) { - const overwrite = await vscode.window.showInformationMessage('A merged.lsx file already exists. Do you want to overwrite it?', 'Yes', 'No'); - if (overwrite === 'No') { - return; // Stop execution if user chooses not to overwrite - } - } + // Create atlas and texture + try { + await createAtlas(iconsDirPath[0].fsPath, atlasPath, texturePath, newUuid); + vscode.window.showInformationMessage('Atlas and texture created successfully.'); + } catch (error) { + vscode.window.showErrorMessage(`Failed to create atlas or texture: ${error.message}`); + } - // Use a skeleton merged.lsx file as a template - const skeletonMergedPath = path.join(__dirname, '../support_files/templates/long_skeleton_files/merged_atlas.lsx'); - let mergedContent = fs.readFileSync(skeletonMergedPath, 'utf8'); + // Check if merged.lsx exists + if (fs.existsSync(mergedPath)) { + const overwrite = await vscode.window.showInformationMessage('A merged.lsx file already exists. Do you want to overwrite it?', 'Yes', 'No'); + if (overwrite === 'No') { + return; // Stop execution if user chooses not to overwrite + } + } - // Replace placeholders in merged.lsx file - mergedContent = mergedContent.replace(/\{uuid\}/g, newUuid); - mergedContent = mergedContent.replace(/\{file_name\}/g, `Icons_${modName}`); - mergedContent = mergedContent.replace(/\{file_path\}/g, `Public/${modName}/Assets/Textures/Icons/Icons_${modName}.dds`); + // Use a skeleton merged.lsx file as a template + const skeletonMergedPath = path.join(__dirname, '../support_files/templates/long_skeleton_files/merged_atlas.lsx'); + let mergedContent = fs.readFileSync(skeletonMergedPath, 'utf8'); - // Write the modified content to the new merged.lsx file - fs.writeFileSync(mergedPath, mergedContent, 'utf8'); - vscode.window.showInformationMessage(`merged.lsx file created/updated successfully at ${mergedDirPath}`); + // Replace placeholders in merged.lsx file + mergedContent = mergedContent.replace(/\{uuid\}/g, newUuid); + mergedContent = mergedContent.replace(/\{file_name\}/g, `Icons_${modName}`); + mergedContent = mergedContent.replace(/\{file_path\}/g, `Public/${modName}/Assets/Textures/Icons/Icons_${modName}.dds`); - } else { - console.log('Pil not found, not running exe for atlas icon stuff') - } - console.log('__createAtlasCommand__'); + // Write the modified content to the new merged.lsx file + fs.writeFileSync(mergedPath, mergedContent, 'utf8'); + vscode.window.showInformationMessage(`merged.lsx file created/updated successfully at ${mergedDirPath}`); }); module.exports = createAtlasCommand; diff --git a/commands/createFileFromTemplate.js b/commands/createFileFromTemplate.js index e9c1cc35..6bc15c4d 100644 --- a/commands/createFileFromTemplate.js +++ b/commands/createFileFromTemplate.js @@ -2,64 +2,129 @@ const vscode = require('vscode'); const path = require('path'); const fs = require('fs'); -const { insertText } = require('../support_files/helper_functions'); +const { insertText, getModName } = require('../support_files/helper_functions'); const { getConfig } = require('../support_files/config'); +const templates = require('../support_files/templates/skeleton_files'); +const locales = [ + "BrazilianPortuguese", + "Chinese", + "ChineseTraditional", + "English", + "French", + "German", + "Italian", + "Japanese", + "Korean", + "LatinSpanish", + "Polish", + "Russian", + "Spanish", + "Turkish", + "Ukrainian", +] -let createFileFromTemplateCommand = vscode.commands.registerCommand('bg3-mod-helper.createFileFromTemplate', () => { - const templates = require('../support_files/templates/skeleton_files'); +let createFileFromTemplateCommand = vscode.commands.registerCommand('bg3-mod-helper.createFileFromTemplate', async () => { const templateNames = Object.keys(templates); - - vscode.window.showQuickPick(templateNames, { + const selectedTemplateName = await vscode.window.showQuickPick(templateNames, { placeHolder: 'Type to filter templates...', matchOnDetail: true - }).then(selectedTemplateName => { - if (selectedTemplateName) { - const templatePath = templates[selectedTemplateName]; - - // Debugging: Log the full path - const fullPath = path.join(__dirname, templatePath); - console.log(`Full path to template: ${fullPath}`); - - let fileContent; - - if (templatePath.endsWith('.lsx')) { - // Read file content if it's a .lsx file - fileContent = fs.readFileSync(path.join(__dirname, templatePath), 'utf8'); - } else { - // Handle other templates as before - fileContent = templates[selectedTemplateName]; - } - - const workspaceFolders = vscode.workspace.workspaceFolders; - if (workspaceFolders) { - const filePath = path.join(workspaceFolders[0].uri.fsPath, selectedTemplateName); - const fileUri = vscode.Uri.file(filePath); - - vscode.workspace.fs.writeFile(fileUri, Buffer.from(fileContent)).then(() => { - vscode.window.showInformationMessage(`${selectedTemplateName} created successfully.`); - vscode.window.showTextDocument(fileUri); - }, err => { - vscode.window.showErrorMessage(`Error creating ${selectedTemplateName}: ${err}`); - }); - } else { - vscode.window.showErrorMessage('No workspace folder found.'); - } - } }); + + if (!selectedTemplateName) { + return; + } + + const templatePath = templates[selectedTemplateName]; + const fullPath = path.join(__dirname, templatePath); + console.log(`Full path to template: ${fullPath}`); + + let fileContent; + if (templatePath.endsWith('.lsx')) { + // Read file content if it's a .lsx file + fileContent = fs.readFileSync(path.join(__dirname, templatePath), 'utf8'); + } else { + // Handle other templates as before + fileContent = templates[selectedTemplateName]; + } + + let fileName = selectedTemplateName; + if (selectedTemplateName === 'Localization loca.xml') { + const { fileName: localeFileName, fileContent: localeFileContent, selectedLocale } = await handleLocalizationTemplate(fileContent); + if (!localeFileName) { + return; + } + fileName = localeFileName; + fileContent = localeFileContent; + await createFileInWorkspace(fileName, fileContent, selectedLocale); + } else { + await createFileInWorkspace(selectedTemplateName, fileContent); + } }); +async function handleLocalizationTemplate(fileContent) { + const selectedLocale = await vscode.window.showQuickPick(locales, { + placeHolder: 'Select a locale for the .loca.xml file' + }); + + if (!selectedLocale) { + vscode.window.showInformationMessage('No locale selected. Operation cancelled.'); + return { fileName: null, fileContent: null, selectedLocale: null }; + } + + const modName = await getModName(); + const fileName = `${modName}_${selectedLocale}.loca.xml`; + return { fileName, fileContent, selectedLocale }; +} + +async function createFileInWorkspace(fileName, fileContent, locale = '') { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + vscode.window.showErrorMessage('No workspace folder found.'); + return; + } + + const config = getConfig(); + const rootModPath = config.rootModPath; + let fileOutdir = rootModPath; + if (locale) { + const localizationPath = path.join(rootModPath, 'Localization', locale); + + // Ensure the Localization folder exists + if (!fs.existsSync(localizationPath)) { + fs.mkdirSync(localizationPath, { recursive: true }); + } + fileOutdir = localizationPath; + } + + const filePath = path.join(fileOutdir, fileName); + const fileUri = vscode.Uri.file(filePath); + + if (fs.existsSync(filePath)) { + vscode.window.showInformationMessage(`${fileName} already exists at ${filePath} and will not be overwritten.`); + return; + } + + try { + await vscode.workspace.fs.writeFile(fileUri, Buffer.from(fileContent)); + vscode.window.showInformationMessage(`${fileName} created successfully at ${filePath}.`); + vscode.window.showTextDocument(fileUri); + } catch (err) { + vscode.window.showErrorMessage(`Error creating ${fileName}: ${err}`); + } +} + let insertAttributeCommand = vscode.commands.registerCommand('bg3-mod-helper.insertAttribute', function () { const editor = vscode.window.activeTextEditor; if (!editor) return; - + const text = editor.document.getText(); const attributeLines = extractAttributeLines(text, ''); if (attributeLines.length === 0) { vscode.window.showInformationMessage('No custom attributes found.'); return; } - + vscode.window.showQuickPick(attributeLines).then(selectedLine => { if (selectedLine) { insertText(selectedLine); @@ -70,14 +135,14 @@ let insertAttributeCommand = vscode.commands.registerCommand('bg3-mod-helper.ins let insertClipboardCommand = vscode.commands.registerCommand('bg3-mod-helper.insertClipboard', function () { const editor = vscode.window.activeTextEditor; if (!editor) return; - + const text = editor.document.getText(); const clipboardLines = extractAttributeLines(text, ''); if (clipboardLines.length === 0) { vscode.window.showInformationMessage('No clipboard content found.'); return; } - + vscode.window.showQuickPick(clipboardLines).then(selectedLine => { if (selectedLine) { insertText(selectedLine); @@ -91,4 +156,4 @@ function extractAttributeLines(text, startDelimiter, endDelimiter) { const end = lines.findIndex(line => line.includes(endDelimiter), start); if (start === -1 || end === -1 || start >= end) return []; return lines.slice(start + 1, end); -} \ No newline at end of file +} diff --git a/commands/insertHandleUUID.js b/commands/insertHandleUUID.js index 2abe1643..231f040d 100644 --- a/commands/insertHandleUUID.js +++ b/commands/insertHandleUUID.js @@ -73,7 +73,7 @@ async function updateLocaXmlFiles(changes) { let selectedLocaFiles = locaFiles; // If user doesn't want to add handles to all loca files, prompt for selection from the list of all loca files - if (!addHandlesToAllLocas) { + if (!addHandlesToAllLocas && locaFiles.length > 1) { const fileItems = locaFiles.map(file => ({ label: path.basename(file.fsPath), description: path.relative(workspaceFolder.uri.fsPath, file.fsPath), @@ -134,7 +134,12 @@ async function updateLocaXmlFile(locaFileUri, changes, edit) { } function generateContent(handle, handleContent) { - return ` ${handleContent.trim()}\n`; + function convertNewlinesToBr(text) { + return text.replace(/(\\r\\n|\\n|\\r)/g, '<br>'); + } + + const preparedContent = convertNewlinesToBr(handleContent).trim(); + return ` ${preparedContent}\n`; } function generateHandle() { diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 54ad29b9..1cf9e348 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,6 +1,6 @@ { "name": "bg3-mod-helper", - "version": "3.3.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package-lock.json b/package-lock.json index e6593f4a..b90624f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bg3-mod-helper", - "version": "2.1.62", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bg3-mod-helper", - "version": "2.1.62", + "version": "2.2.0", "license": "LGPL-3.0-or-later", "dependencies": { "log4js": "^6.9.1", diff --git a/package.json b/package.json index 3ff70242..6d82f729 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "bg3_mod_helper", "publisher": "ghostboats", "description": "This extension is designed to help you make mods in Baldur's Gate 3 by creating UUIDs and handles for you, as well as updating your .loca.xml files as well should they exist. And more to come in the future.", - "version": "2.1.62", + "version": "2.2.0", "icon": "media/marketplace_icon.png", "engines": { "vscode": "^1.86.0" diff --git a/support_files/scripts/python/add_icons_to_atlas.py b/support_files/scripts/python/add_icons_to_atlas.py deleted file mode 100644 index 19c1eac2..00000000 --- a/support_files/scripts/python/add_icons_to_atlas.py +++ /dev/null @@ -1,273 +0,0 @@ -import os -import subprocess -from dataclasses import dataclass -from pathlib import Path -import argparse -import timeit - -# External -from PIL import Image -import numpy - - -#script_dir = Path(os.path.dirname(os.path.abspath(__file__))) -#os.chdir(script_dir) - -atlas_template = """ - - - - - {icons} - - - - - - - - - - - - - - - - - - - - - - -""" - -@dataclass -class UV(): - u1:float - v1:float - u2:float - v2:float - -@dataclass -class Icon(): - image_path:Path - pos:tuple[float,float] - uv:UV - image:Image.Image = None - - @property - def name(self): - return self.image_path.stem - - def __post_init__(self): - self.image = Image.open(self.image_path).convert("RGBA") - - def to_xml(self): - return """ - - - - - - - """.format( - name = self.name, - u1 = self.uv.u1, - v1 = self.uv.v1, - u2 = self.uv.u2, - v2 = self.uv.v2 - ) - -def get_images(directory): - dir_path = Path(directory) - files = dir_path.glob("*.png") - return list(files) - -def truncate(number, digits, round_num = True) -> float: - stepper = 10.0 ** digits - if round_num: - return round(numpy.trunc(stepper * number) / stepper, digits - 1) - else: - return numpy.trunc(stepper * number) / stepper - -script_dir = Path(os.path.dirname(os.path.abspath(__file__))) -os.chdir(script_dir) - - -totalIcons = 0 - -texture_resource_template = """ - - - - - - - - - - - - - - - - - - -""" - - - -def get_icons(icons_dir:Path, icon_size:tuple[int,int], texture_size:tuple[int,int])->list[Icon]: - icons = [] - padding = (float(0.5/texture_size[0]), float(0.5/texture_size[1])) - col_max = texture_size[0] / icon_size[0] - row_max = texture_size[1] / icon_size[1] - - x = 0 - y = 0 - - # For some reason, the editor appends the first two icons to the end - icons_first = [] - images = get_images(icons_dir) - for img in images: - # u1 = truncate(float(((icon_size * x) / texture_size) + padding), 9) - # v1 = truncate(float(((icon_size * y) / texture_size) + padding), 9) - # u2 = truncate(float(((icon_size * (x + 1)) / texture_size) - padding), 9) - # v2 = truncate(float(((icon_size * (y + 1)) / texture_size) - padding), 9) - round_num = True - truncate_u1 = 7 - truncate_v1 = 8 - truncate_u2 = 7 - truncate_v2 = 7 - - if x <= 1 and y == 0: - u1 = truncate(numpy.clip(float(((icon_size[0] * x) / texture_size[0]) + padding[0]), 0, 1.0), 9, round_num=True) - v1 = truncate(numpy.clip(float(((icon_size[1] * y) / texture_size[0]) + padding[0]), 0, 1.0), 9, round_num=False) - u2 = truncate(numpy.clip(float(((icon_size[0] * (x + 1)) / texture_size[1]) - padding[1]), 0, 1.0), 7, round_num=False) - v2 = truncate(numpy.clip(float(((icon_size[1] * (y + 1)) / texture_size[1]) - padding[1]), 0, 1.0), 7, round_num=False) - else: - u1 = truncate(numpy.clip(float(((icon_size[0] * x) / texture_size[0]) + padding[0]), 0, 1.0), truncate_u1, round_num) - v1 = truncate(numpy.clip(float(((icon_size[1] * y) / texture_size[0]) + padding[0]), 0, 1.0), truncate_v1, round_num) - u2 = truncate(numpy.clip(float(((icon_size[0] * (x + 1)) / texture_size[1]) - padding[1]), 0, 1.0), truncate_u2, round_num) - v2 = truncate(numpy.clip(float(((icon_size[1] * (y + 1)) / texture_size[1]) - padding[1]), 0, 1.0), truncate_v2, round_num) - - icon = Icon(img.resolve(), (x * icon_size[0], y * icon_size[1]), UV(u1, v1, u2, v2)) - - if x <= 1 and y == 0: - icons_first.append(icon) - else: - icons.append(icon) - - x += 1 - if(x >= col_max): - y += 1 - x = 0 - if (y > row_max): - break - - icons.extend(icons_first) - return icons - -def generate_texture(icons:list[Icon], texture_output:Path, texture_size:tuple[int,int], dds_format:str = "DXT5", do_mipmaps:bool=False): - temp_texture_png = texture_output.with_suffix(".png") - texture_image = Image.new('RGBA', texture_size, (0, 0, 0, 0)) - for icon in icons: - #texture_image.paste(icon.image, icon.pos, mask=0) - texture_image.alpha_composite(icon.image, icon.pos) - - - texture_image.save(temp_texture_png) - - - # -m 1 disables mimaps - command = f"texconv -m {12 if do_mipmaps else 1} -ft DDS -f {dds_format} -nologo -timing -y -o \"{texture_output.parent}\" \"{temp_texture_png.absolute()}\"" - p = subprocess.run(command, - shell=True, - universal_newlines=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - if p.returncode == 0: - os.remove(str(temp_texture_png.absolute()).replace("/", "\\")) - return True - -def generate_atlas_lsx(icons:list[Icon], atlas_output:Path, texture_output:Path, atlas_uuid:str, icon_size:tuple[int,int], texture_size:tuple[int,int])->bool: - if atlas_uuid is None or atlas_uuid == "": - #atlas_uuid = common.NewUUID() - atlas_uuid = '6b3e6d54-dd4d-4ff1-89e4-f131e68e9035' - - atlas_output = atlas_output.with_suffix(".lsx") - texture_output = texture_output.with_suffix(".dds") - root_data_dir = atlas_output.parent.parent.parent - - def create_atlas_output(icons_str, icon_w, icon_h, - texture_path, uuid, texture_width, texture_height): - return atlas_template.format( - icons = icons_str, - icon_w = icon_w, - icon_h = icon_h, - texture_path = texture_path, - texture_uuid = uuid, - texture_width = texture_width, - texture_height = texture_height - ) - - icons_str = "" - for icon in icons: - icons_str += icon.to_xml() - - atlas_output.parent.mkdir(exist_ok=True, parents=True) - texture_output.parent.mkdir(exist_ok=True, parents=True) - - xml_str = create_atlas_output(icons_str, icon_size[0], icon_size[1], - texture_output.relative_to(root_data_dir), atlas_uuid, texture_size[0], texture_size[1]) - - - f = open(atlas_output, "w") - f.write(xml_str) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Create a texture atlas from a folder of icons.') - parser.add_argument("-i", "--icons", type=Path, required=True, help='The directory of icons to process.') - parser.add_argument("-a", "--atlas", type=Path, required=True, help='The path of the atlas file to create, such as Public/ModName_UUID/GUI/MyAtlas.lsx.') - parser.add_argument("-t", "--texture", type=Path, required=True, help='The path of the texture to create.') - parser.add_argument("-u", "--uuid", type=str, default="", help='The UUID to use for the atlas (defaults to a new UUID4).') - parser.add_argument("-m", "--mipmaps", action='store_true', help='Generate mipmaps (default False).') - parser.add_argument("-f", "--ddsformat", type=str, default="DXT5", help='The dds format to use (DXT1, DXT5 etc). Defaults to DXT5 (BC3_UNORM).') - parser.add_argument("-r", "--resource", type=Path, help='The path to content texture resource lsf to generate (optional). This requires --divine to be set, or a LSLIB_PATH environment variable to be set.') - parser.add_argument("--resourcelsx", type=Path, help='Optional path to output the content resource to, in lsx format.') - parser.add_argument("--ddstool", type=Path, help='The path to the "DirectXTex texture processing library" (directory where texconv is).') - parser.add_argument("--iconsize", type=int, default=(64,64), nargs="+", help='The icon width/height/.') - parser.add_argument("--texturesize", type=int, default=(2048,2048), nargs="+", help='The texture width/height/.') - parser.add_argument("--divine", type=Path, help="The path to divine.exe.") - parser.usage = f""" - Example usage: - python create_atlas.py -i "G:/Modding/BG3/Mods/MyMod/Icons" -a "C:/BG3/Data/Public/MyModFolder/GUI/MyMod_Icons.lsx" -u 6bae909c-1736-48e7-ae19-314b3aa7b1f5 -t "C:/BG3/Data/Public/MyModFolder/Assets/Textures/Icons/MyMod_Icons.dds" --ddstool "C:/Portable/DirectXTex" --texturesize 1024 1024 - """ - def run_cmd(): - args = parser.parse_args() - lslib_dll:Path = args.divine.is_dir() and args.divine.joinpath("LSLib.dll") or args.divine.parent.joinpath("LSLib.dll") - icons_dir:Path = args.icons - atlas_output:Path = args.atlas - texture_output:Path = args.texture - # resource_output:Path = args.resource - # resource_lsx_output:Path = args.resourcelsx - atlas_uuid:str = args.uuid - dds_format:str = args.ddsformat - icon_size:tuple[int,int] = args.iconsize - texture_size:tuple[int,int] = args.texturesize - do_mipmaps:bool = args.mipmaps - icons = get_icons(icons_dir, icon_size, texture_size) - global totalIcons - totalIcons = len(icons) - if totalIcons > 0: - generate_atlas_lsx(icons, atlas_output, texture_output, atlas_uuid, icon_size, texture_size) - generate_texture(icons, texture_output, texture_size, dds_format, do_mipmaps) - # if resource_output: - # generate_texture_lsf(atlas_uuid, texture_output, resource_output, lslib_dll, resource_lsx_output) - - print("Created atlas in {} seconds for {} icons.".format(timeit.timeit(run_cmd, number=1), totalIcons)) \ No newline at end of file diff --git a/support_files/scripts/python/check_pil.py b/support_files/scripts/python/check_pil.py deleted file mode 100644 index f7199daa..00000000 --- a/support_files/scripts/python/check_pil.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - import PIL -except ImportError: - print("PIL (Pillow) is not installed.") \ No newline at end of file diff --git a/support_files/scripts/python/check_pythonnet.py b/support_files/scripts/python/check_pythonnet.py deleted file mode 100644 index faa46195..00000000 --- a/support_files/scripts/python/check_pythonnet.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - import clr -except ImportError: - print("clr is not installed") \ No newline at end of file diff --git a/support_files/scripts/python/check_wand.py b/support_files/scripts/python/check_wand.py deleted file mode 100644 index 9fd76da9..00000000 --- a/support_files/scripts/python/check_wand.py +++ /dev/null @@ -1,14 +0,0 @@ -try: - # Check if Wand package is installed - import wand -except ImportError: - print("Wand not installed") - exit() - -# Separate check for ImageMagick -try: - # Check if Wand image module works (requires ImageMagick) - import wand.image - print("Wand and ImageMagick are installed") -except ImportError: - print("ImageMagick not installed") diff --git a/support_files/templates/skeleton_files.js b/support_files/templates/skeleton_files.js index 3bcdc825..b331ffba 100644 --- a/support_files/templates/skeleton_files.js +++ b/support_files/templates/skeleton_files.js @@ -1,6 +1,6 @@ // skeleton_files.js const templates = { - 'RENAME_ME.loca.xml': `\n\n`, + 'Localization loca.xml': `\n\n`, 'Progressions.lsx': `