diff --git a/plugins.json b/plugins.json index 79dab3ef..dc975954 100644 --- a/plugins.json +++ b/plugins.json @@ -1534,5 +1534,16 @@ "website": "https://svdex.moe", "min_version": "4.8.0", "has_changelog": true + }, + "name_guard": { + "title": "Name Guard", + "author": "Dedou3D", + "description": "Clean folder and cube names for safe ModelEngine exports.", + "icon": "drive_file_rename_outline", + "version": "1.0.0", + "variant": "both", + "min_version": "4.0.0", + "new_repository_format": true, + "tags": ["Minecraft", "ModelEngine", "Utility"] } } diff --git a/plugins/name_guard/about.md b/plugins/name_guard/about.md new file mode 100644 index 00000000..4954e8be --- /dev/null +++ b/plugins/name_guard/about.md @@ -0,0 +1,31 @@ +# Name Guard + +Name Guard cleans folder and cube names so your models export safely. + +Before: + +`Right Arm` +- `Cube` +- `Cube` + +After: + +`right_arm` +- `right_arm` +- `right_arm` + +## What It Does + +- Converts names to lowercase +- Removes accents +- Replaces spaces and invalid characters with `_` +- Cleans folder names +- Renames cubes from their parent folder name +- Keeps root cubes safe by cleaning their own names + +## Usage + +Open your model, then run: + +`Filter > Clean Names` + diff --git a/plugins/name_guard/name_guard.js b/plugins/name_guard/name_guard.js new file mode 100644 index 00000000..edae2f25 --- /dev/null +++ b/plugins/name_guard/name_guard.js @@ -0,0 +1,114 @@ +(function() { + 'use strict'; + + const PLUGIN_ID = 'name_guard'; + const FALLBACK_NAME = 'part'; + + let cleanNamesAction; + + function sanitizeName(value, fallback = FALLBACK_NAME) { + const sanitized = String(value || '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9_.-]+/g, '_') + .replace(/_+/g, '_') + .replace(/^_+|_+$/g, ''); + + return sanitized || fallback; + } + + function uniqueName(baseName, usedNames) { + let name = baseName; + let index = 2; + + while (usedNames.has(name)) { + name = `${baseName}_${index}`; + index += 1; + } + + usedNames.add(name); + return name; + } + + function getChildGroups(group) { + return group.children.filter(child => child instanceof Group); + } + + function getChildCubes(group) { + return group.children.filter(child => child instanceof Cube); + } + + function renameGroupTree(group, usedNamesByGroup) { + const parent = group.parent instanceof Group ? group.parent : null; + const usedNames = usedNamesByGroup.get(parent) || new Set(); + usedNamesByGroup.set(parent, usedNames); + + group.name = uniqueName(sanitizeName(group.name), usedNames); + + const childGroupNames = new Set(); + usedNamesByGroup.set(group, childGroupNames); + getChildGroups(group).forEach(childGroup => renameGroupTree(childGroup, usedNamesByGroup)); + + const cubeBaseName = sanitizeName(group.name); + getChildCubes(group).forEach(cube => { + cube.name = cubeBaseName; + }); + } + + function cleanRootCubes() { + const usedNames = new Set(); + Cube.all + .filter(cube => !(cube.parent instanceof Group)) + .forEach(cube => { + cube.name = uniqueName(sanitizeName(cube.name), usedNames); + }); + } + + function cleanNames() { + if (!Project) { + Blockbench.showQuickMessage('Open or create a project first.'); + return; + } + + Undo.initEdit({outliner: true, elements: Cube.all}); + + const rootGroups = Group.all.filter(group => !(group.parent instanceof Group)); + const usedNamesByGroup = new Map(); + rootGroups.forEach(group => renameGroupTree(group, usedNamesByGroup)); + cleanRootCubes(); + + Undo.finishEdit('Clean names'); + Canvas.updateAll(); + Blockbench.showQuickMessage('Names cleaned.'); + } + + Plugin.register(PLUGIN_ID, { + title: 'Name Guard', + author: 'Dedou3D', + description: 'Clean folder and cube names for safe ModelEngine exports.', + icon: 'drive_file_rename_outline', + version: '1.0.0', + variant: 'both', + min_version: '4.0.0', + new_repository_format: true, + tags: ['Minecraft', 'ModelEngine', 'Utility'], + onload() { + cleanNamesAction = new Action('clean_name_guard_names', { + name: 'Clean Names', + description: 'Clean folder and cube names for safe exports.', + icon: 'drive_file_rename_outline', + category: 'edit', + click: cleanNames + }); + + MenuBar.addAction(cleanNamesAction, 'filter'); + }, + onunload() { + if (cleanNamesAction) { + cleanNamesAction.delete(); + cleanNamesAction = null; + } + } + }); +})();