Skip to content

Instantly share code, notes, and snippets.

@inxomnyaa
Last active August 13, 2025 22:45
Show Gist options
  • Save inxomnyaa/82fdc161765ecf71d848e3fe81b6a0c8 to your computer and use it in GitHub Desktop.
Save inxomnyaa/82fdc161765ecf71d848e3fe81b6a0c8 to your computer and use it in GitHub Desktop.
WorldPainter External Viewer Script with Persistent Configuration (vibecoded with Claude)
// 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