Last active
August 13, 2025 22:45
-
-
Save inxomnyaa/82fdc161765ecf71d848e3fe81b6a0c8 to your computer and use it in GitHub Desktop.
WorldPainter External Viewer Script with Persistent Configuration (vibecoded with Claude)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WorldPainter External Viewer Script with Persistent Configuration | |
// This script exports the current world and opens it in external 3D viewers | |
// Import required Java classes | |
var Properties = Java.type("java.util.Properties"); | |
var FileInputStream = Java.type("java.io.FileInputStream"); | |
var FileOutputStream = Java.type("java.io.FileOutputStream"); | |
var File = Java.type("java.io.File"); | |
var JOptionPane = Java.type("javax.swing.JOptionPane"); | |
var JPanel = Java.type("javax.swing.JPanel"); | |
var JLabel = Java.type("javax.swing.JLabel"); | |
var JTextField = Java.type("javax.swing.JTextField"); | |
var JButton = Java.type("javax.swing.JButton"); | |
var BorderLayout = Java.type("java.awt.BorderLayout"); | |
var GridLayout = Java.type("java.awt.GridLayout"); | |
// Configuration file path | |
var configDir = java.lang.System.getProperty("user.home") + java.io.File.separator + ".worldpainter"; | |
var configFile = configDir + java.io.File.separator + "external_viewers.properties"; | |
// Default viewer paths (Windows - adjust for your OS) | |
var DEFAULT_PATHS = { | |
"amulet": "C:\\Program Files\\Amulet-Map-Editor\\amulet_map_editor.exe", | |
"mineways": "C:\\Program Files\\Mineways\\Mineways.exe", | |
"chunky": "C:\\Users\\%USERNAME%\\AppData\\Local\\Chunky\\chunky.exe", | |
"avoyd": "C:\\Users\\%USERNAME%\\AppData\\Local\\Avoyd\\avoyd.exe" | |
}; | |
function loadConfig() { | |
try { | |
var props = new Properties(); | |
var configFileObj = new File(configFile); | |
if (configFileObj.exists()) { | |
var input = new FileInputStream(configFile); | |
props.load(input); | |
input.close(); | |
var config = {}; | |
var keys = props.stringPropertyNames().toArray(); | |
for (var i = 0; i < keys.length; i++) { | |
config[keys[i]] = props.getProperty(keys[i]); | |
} | |
return config; | |
} else { | |
// Return default config if file doesn't exist | |
return DEFAULT_PATHS; | |
} | |
} catch (error) { | |
print("Error loading config: " + error.message); | |
return DEFAULT_PATHS; | |
} | |
} | |
function saveConfig(config) { | |
try { | |
// Ensure config directory exists | |
var configDirObj = new File(configDir); | |
if (!configDirObj.exists()) { | |
configDirObj.mkdirs(); | |
} | |
var props = new Properties(); | |
for (var key in config) { | |
if (config[key]) { // Only save non-empty paths | |
props.setProperty(key, config[key]); | |
} | |
} | |
var output = new FileOutputStream(configFile); | |
props.store(output, "WorldPainter External Viewer Configuration"); | |
output.close(); | |
print("Configuration saved to: " + configFile); | |
return true; | |
} catch (error) { | |
print("Error saving config: " + error.message); | |
return false; | |
} | |
} | |
function showConfigDialog() { | |
try { | |
var currentConfig = loadConfig(); | |
// Import additional Swing components for file browser | |
var JFileChooser = Java.type("javax.swing.JFileChooser"); | |
var JFrame = Java.type("javax.swing.JFrame"); | |
var JDialog = Java.type("javax.swing.JDialog"); | |
var BoxLayout = Java.type("javax.swing.BoxLayout"); | |
var Box = Java.type("javax.swing.Box"); | |
var Dimension = Java.type("java.awt.Dimension"); | |
var FlowLayout = Java.type("java.awt.FlowLayout"); | |
// Create main dialog | |
var frame = new JFrame(); | |
var dialog = new JDialog(frame, "Configure External Viewers", true); | |
dialog.setLayout(new BoxLayout(dialog.getContentPane(), BoxLayout.Y_AXIS)); | |
var fields = {}; | |
var viewers = ["amulet", "mineways", "chunky", "avoyd"]; | |
// Add input fields with browse buttons for each viewer | |
for (var i = 0; i < viewers.length; i++) { | |
var viewer = viewers[i]; | |
// Create panel for this viewer | |
var viewerPanel = new JPanel(new BorderLayout(5, 5)); | |
viewerPanel.add(new JLabel(viewer.charAt(0).toUpperCase() + viewer.slice(1) + " Path:"), BorderLayout.WEST); | |
// Text field | |
var field = new JTextField(currentConfig[viewer] || "", 30); | |
fields[viewer] = field; | |
viewerPanel.add(field, BorderLayout.CENTER); | |
// Browse button | |
var browseButton = new JButton("Browse..."); | |
// Create closure to capture the current field and viewer | |
(function(currentField, currentViewer) { | |
browseButton.addActionListener(function(e) { | |
var fileChooser = new JFileChooser(); | |
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); | |
fileChooser.setDialogTitle("Select " + currentViewer.charAt(0).toUpperCase() + currentViewer.slice(1) + " Executable"); | |
// Set initial directory to Program Files if field is empty | |
if (currentField.getText().trim() === "") { | |
var programFiles = "C:\\Program Files"; | |
var programFilesDir = new File(programFiles); | |
if (programFilesDir.exists()) { | |
fileChooser.setCurrentDirectory(programFilesDir); | |
} | |
} else { | |
// Set to current file's directory if path exists | |
var currentFile = new File(currentField.getText()); | |
if (currentFile.exists() && currentFile.getParentFile() && currentFile.getParentFile().exists()) { | |
fileChooser.setCurrentDirectory(currentFile.getParentFile()); | |
} | |
} | |
// Add file filter for executables | |
var FileNameExtensionFilter = Java.type("javax.swing.filechooser.FileNameExtensionFilter"); | |
var exeFilter = new FileNameExtensionFilter("Executable Files (*.exe)", "exe"); | |
fileChooser.setFileFilter(exeFilter); | |
var result = fileChooser.showOpenDialog(dialog); | |
if (result === JFileChooser.APPROVE_OPTION) { | |
var selectedFile = fileChooser.getSelectedFile(); | |
currentField.setText(selectedFile.getAbsolutePath()); | |
} | |
}); | |
})(field, viewer); | |
viewerPanel.add(browseButton, BorderLayout.EAST); | |
dialog.add(viewerPanel); | |
dialog.add(Box.createVerticalStrut(10)); // spacing | |
} | |
// Add buttons panel | |
var buttonPanel = new JPanel(new FlowLayout()); | |
var okButton = new JButton("OK"); | |
var cancelButton = new JButton("Cancel"); | |
var dialogResult = {value: JOptionPane.CANCEL_OPTION}; | |
okButton.addActionListener(function(e) { | |
dialogResult.value = JOptionPane.OK_OPTION; | |
dialog.dispose(); | |
}); | |
cancelButton.addActionListener(function(e) { | |
dialogResult.value = JOptionPane.CANCEL_OPTION; | |
dialog.dispose(); | |
}); | |
buttonPanel.add(okButton); | |
buttonPanel.add(cancelButton); | |
dialog.add(buttonPanel); | |
// Configure and show dialog | |
dialog.pack(); | |
dialog.setLocationRelativeTo(null); | |
dialog.setVisible(true); | |
if (dialogResult.value === JOptionPane.OK_OPTION) { | |
// Save new configuration | |
var newConfig = {}; | |
for (var viewer in fields) { | |
newConfig[viewer] = fields[viewer].getText().trim(); | |
} | |
if (saveConfig(newConfig)) { | |
print("Configuration updated successfully!"); | |
return newConfig; | |
} | |
} | |
return currentConfig; | |
} catch (error) { | |
print("Error showing config dialog: " + error.message); | |
return loadConfig(); | |
} | |
} | |
function openInExternalViewer(viewerType) { | |
try { | |
var config = loadConfig(); | |
var viewerPath = config[viewerType.toLowerCase()]; | |
if (!viewerPath || viewerPath.trim() === "") { | |
print("No path configured for " + viewerType + "!"); | |
print("Run the script again and use 'Configure Viewer Paths' to set up viewer paths."); | |
return; | |
} | |
// Check if viewer exists | |
var viewerFile = new File(viewerPath); | |
if (!viewerFile.exists()) { | |
print("Viewer not found at: " + viewerPath); | |
print("Run the script again and use 'Configure Viewer Paths' to update the path."); | |
return; | |
} | |
// Use the global 'world' variable that's available in WorldPainter scripts | |
if (!world) { | |
print("No world is currently open in WorldPainter!"); | |
print("Please open or create a world first."); | |
return; | |
} | |
// // Check if world is saved (needed for export settings) | |
// var worldFile = world.getFile(); | |
// if (!worldFile) { | |
// print("Please save your world first!"); | |
// print("WorldPainter needs export settings stored in the .world file."); | |
// print("You can:"); | |
// print("1. Save your world (Ctrl+S)"); | |
// print("2. Or try File > Export > Export as Minecraft map... once, then cancel and save"); | |
// return; | |
// } | |
// Create temporary export directory | |
var tempDir = java.lang.System.getProperty("java.io.tmpdir"); | |
var worldName = "wp_temp_export_" + Date.now(); | |
var exportPath = tempDir + java.io.File.separator + worldName; | |
print("Exporting world to: " + exportPath); | |
try { | |
// Create export directory | |
var exportDir = new File(exportPath); | |
if (!exportDir.exists()) { | |
exportDir.mkdir(); | |
} | |
// Export using the current loaded world | |
wp.exportWorld(world).toDirectory(exportPath).go(); | |
print("Export completed! Opening in " + viewerType + "..."); | |
// The exported world will be in a subdirectory named after the world | |
var worldSubdir = exportPath + java.io.File.separator + world.getName(); | |
// Launch the viewer | |
var command = [viewerPath, worldSubdir]; | |
var processBuilder = new java.lang.ProcessBuilder(command); | |
processBuilder.start(); | |
print("Launched " + viewerType + " successfully!"); | |
print("Exported world location: " + worldSubdir); | |
} catch (exportError) { | |
print("Export failed: " + exportError.message); | |
print(""); | |
print("This usually means export settings are not configured."); | |
print("To fix this:"); | |
print("1. In WorldPainter: File > Export > Export as Minecraft map..."); | |
print("2. Configure your export settings (but don't complete the export)"); | |
print("3. Click Cancel to close the export dialog"); | |
print("4. Save your world (Ctrl+S) to store the export settings"); | |
print("5. Run this script again"); | |
print(""); | |
print("Alternative: Export manually and open the folder in " + viewerType + " directly."); | |
} | |
} catch (error) { | |
print("Error: " + error.message); | |
print("Make sure you have a world open and saved in WorldPainter."); | |
} | |
} | |
function exportAndWatch() { | |
try { | |
// Use the global 'world' variable available in WorldPainter scripts | |
if (!world) { | |
print("No world is currently open!"); | |
return; | |
} | |
print("Setting up auto-export..."); | |
// Create export directory in temp folder | |
var tempDir = java.lang.System.getProperty("java.io.tmpdir"); | |
var exportDirName = "wp_persistent_export_" + Date.now(); | |
var exportDir = tempDir + java.io.File.separator + exportDirName; | |
print("Setting up auto-export to: " + exportDir); | |
// Export current state using the correct API | |
wp.exportWorld(world).toDirectory(exportDir).go(); | |
// Find the actual world subdirectory | |
var exportDirFile = new File(exportDir); | |
var subdirs = exportDirFile.listFiles(); | |
var actualWorldDir = exportDir; | |
if (subdirs && subdirs.length > 0) { | |
for (var i = 0; i < subdirs.length; i++) { | |
if (subdirs[i].isDirectory()) { | |
actualWorldDir = subdirs[i].getAbsolutePath(); | |
break; | |
} | |
} | |
} | |
print("Initial export complete! You can now:"); | |
print("1. Open " + actualWorldDir + " in Amulet, MineWays, or your preferred viewer"); | |
print("2. Run this script again after making changes to update the export"); | |
print("3. Set up file watching in your external viewer if supported"); | |
return actualWorldDir; | |
} catch (error) { | |
print("Error during export: " + error.message); | |
print("Make sure you have a world open in WorldPainter."); | |
} | |
} | |
function listAvailableViewers() { | |
var config = loadConfig(); | |
print("\n=== Configured Viewers ==="); | |
for (var viewer in config) { | |
var path = config[viewer]; | |
var status = "NOT FOUND"; | |
if (path && path.trim() !== "") { | |
var viewerFile = new File(path); | |
if (viewerFile.exists()) { | |
status = "OK"; | |
} | |
} else { | |
status = "NOT CONFIGURED"; | |
} | |
print(viewer + ": " + (path || "not set") + " [" + status + "]"); | |
} | |
print(""); | |
} | |
// Main menu functions | |
function showMenu() { | |
print("\n=== WorldPainter External Viewer Helper ==="); | |
print("Configuration:"); | |
print(" configureViewers() - Set up viewer paths (with dialog)"); | |
print(" listViewers() - Show configured viewers and their status"); | |
print(""); | |
print("Quick Actions:"); | |
print(" openInAmulet() - Export and open in Amulet"); | |
print(" openInMineWays() - Export and open in MineWays"); | |
print(" openInChunky() - Export and open in Chunky"); | |
print(" openInAvoyd() - Export and open in Avoyd"); | |
print(""); | |
print("Advanced:"); | |
print(" setupAutoExport() - Setup persistent export folder"); | |
print(" showMenu() - Show this menu again"); | |
print(""); | |
listAvailableViewers(); | |
} | |
// Convenience functions | |
function configureViewers() { | |
return showConfigDialog(); | |
} | |
function listViewers() { | |
listAvailableViewers(); | |
} | |
function openInAmulet() { | |
openInExternalViewer("amulet"); | |
} | |
function openInMineWays() { | |
openInExternalViewer("mineways"); | |
} | |
function openInChunky() { | |
openInExternalViewer("chunky"); | |
} | |
function openInAvoyd() { | |
openInExternalViewer("avoyd"); | |
} | |
function setupAutoExport() { | |
return exportAndWatch(); | |
} | |
// Initialize on first run | |
function initialize() { | |
var config = loadConfig(); | |
var hasValidConfig = false; | |
// Check if we have at least one valid viewer configured | |
for (var viewer in config) { | |
if (config[viewer] && config[viewer].trim() !== "") { | |
var viewerFile = new File(config[viewer]); | |
if (viewerFile.exists()) { | |
hasValidConfig = true; | |
break; | |
} | |
} | |
} | |
if (!hasValidConfig) { | |
print("Welcome! No external viewers are configured yet."); | |
print("Opening configuration dialog..."); | |
// Auto-open configuration dialog on first run | |
showConfigDialog(); | |
} | |
return hasValidConfig; | |
} | |
// Main execution - this runs when the script is executed | |
function main() { | |
var hasValidConfig = initialize(); | |
showMenu(); | |
if (hasValidConfig) { | |
// If we have valid config, show action selection dialog | |
showActionDialog(); | |
} | |
} | |
function showActionDialog() { | |
try { | |
var config = loadConfig(); | |
var actions = []; | |
var actionMap = {}; | |
// Build list of available actions | |
if (config.amulet && new File(config.amulet).exists()) { | |
actions.push("Open in Amulet"); | |
actionMap["Open in Amulet"] = function() { openInExternalViewer("amulet"); }; | |
} | |
if (config.mineways && new File(config.mineways).exists()) { | |
actions.push("Open in MineWays"); | |
actionMap["Open in MineWays"] = function() { openInExternalViewer("mineways"); }; | |
} | |
if (config.chunky && new File(config.chunky).exists()) { | |
actions.push("Open in Chunky"); | |
actionMap["Open in Chunky"] = function() { openInExternalViewer("chunky"); }; | |
} | |
if (config.avoyd && new File(config.avoyd).exists()) { | |
actions.push("Open in Avoyd"); | |
actionMap["Open in Avoyd"] = function() { openInExternalViewer("avoyd"); }; | |
} | |
actions.push("Setup Auto-Export Folder"); | |
actionMap["Setup Auto-Export Folder"] = setupAutoExport; | |
actions.push("Configure Viewer Paths"); | |
actionMap["Configure Viewer Paths"] = showConfigDialog; | |
actions.push("Cancel"); | |
var choice = JOptionPane.showOptionDialog( | |
null, | |
"What would you like to do?", | |
"WorldPainter External Viewer", | |
JOptionPane.DEFAULT_OPTION, | |
JOptionPane.QUESTION_MESSAGE, | |
null, | |
actions, | |
actions[0] | |
); | |
if (choice >= 0 && choice < actions.length - 1) { | |
var selectedAction = actions[choice]; | |
if (actionMap[selectedAction]) { | |
actionMap[selectedAction](); | |
} | |
} | |
} catch (error) { | |
print("Error showing action dialog: " + error.message); | |
} | |
} | |
// Run main function | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment