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': `