Skip to content

Instantly share code, notes, and snippets.

@grom358
Last active December 17, 2015 10:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grom358/5597258 to your computer and use it in GitHub Desktop.
Save grom358/5597258 to your computer and use it in GitHub Desktop.
A PHP script for renaming the part folder names for better ordering when viewing parts in VAB/SPH and also for finding the part in Windows Explorer
<?php
// Category
// Pod is the first tab but is last for purpose of category numbers
$categoryNames = array('Propulsion', 'Control', 'Structural', 'Aero', 'Utility', 'Science', 'Pods');
$subcategoryOrder = array();
// Order of pod parts
// Pods are not separated into subcategory
// Order of propulsion parts
$subcategoryOrder['Propulsion'] = array_flip(array('Fuel Line', 'Liquid Fuel Engine', 'Solid Fuel Booster', 'Jet Engine', 'Misc Engine', 'Liquid Fuel Tank', 'RCS', 'Jet Fuel Tank', 'Oxidizer Tank', 'Air Tank', 'Kethane Tank'));
// Order of control parts
$subcategoryOrder['Control'] = array_flip(array('SAS', 'RCS'));
// Order of structural parts
$subcategoryOrder['Structural'] = array_flip(array('Strut Connector', 'Decoupler', 'Separator', 'Launch Stabilizer', 'Attachment Point', 'Adapter', 'Coupler', 'Multi-Point', 'Girder', 'Beam', 'Panel', 'Frame', 'Fairing', 'Interstage', 'Fuselage', 'Cargo Bay'));
// Order of aero parts
$subcategoryOrder['Aero'] = array_flip(array('Air Intake', 'Nose Cone', 'Wing', 'Control Wing/Surface', 'Engine Body'));
// Order of utility parts
$subcategoryOrder['Utility'] = array_flip(array('Landing Gear', 'Parachute', 'Ladder', 'Electricity', 'Xenon', 'Docking', 'Rover Wheels'));
// Order of science parts
$subcategoryOrder['Science'] = array_flip(array('Antennae', 'Sensor'));
// Fuel line parts
$fuelLineParts = array_flip(array('fuelLine'));
$subcategoryKeywords = array();
// Control keywords => Control subcategory
$subcategoryKeywords['Control'] = array(
'S.A.S' => 'SAS',
'RCS' => 'RCS'
);
// Structural keywords => structural subcategory
$subcategoryKeywords['Structural'] = array(
'Strut Connector' => 'Strut Connector',
'Decoupler' => 'Decoupler',
'Detachment' => 'Decoupler',
'Pylon' => 'Decoupler',
'Separator' => 'Separator',
'Attachment Point' => 'Attachment Point',
'Launch Stability' => 'Launch Stabilizer',
'Coupler' => 'Coupler',
'Girder' => 'Girder',
'Truss' => 'Girder',
'Octagonal' => 'Girder',
'Interstage' => 'Interstage',
'Adapter' => 'Adapter',
'Multi-Point' => 'Multi-Point',
'Macronode' => 'Multi-Point',
'Micronode' => 'Multi-Point',
'Beam' => 'Beam',
'Panel' => 'Panel',
'Frame' => 'Frame',
'Fuselage' => 'Fuselage',
'Fairing' => 'Fairing',
'Cargo Bay' => 'Cargo Bay'
);
// Aero keywords => aero subcategory
$subcategoryKeywords['Aero'] = array(
'Intake' => 'Air Intake',
'Engine Nacelle' => 'Air Intake',
'Engine Body' => 'Engine Body',
'Standard NC' => 'Nose Cone',
'Nose' => 'Nose Cone',
'AV-T1 Winglet' => 'Wing',
'Winglet' => 'Control Wing/Surface',
'Wing' => 'Wing',
'Fin' => 'Wing',
'Canard' => 'Control Wing/Surface',
'Control Surface' => 'Control Wing/Surface'
);
// Utility keywords => Utility subcategory
$subcategoryKeywords['Utility'] = array(
'Landing' => 'Landing Gear',
'Gear Bay' => 'Landing Gear',
'Parachute' => 'Parachute',
'parachute' => 'Parachute',
'Mobility Enhancer' => 'Ladder',
'Solar Array' => 'Electricity',
'Photovoltaic' => 'Electricity',
'Illuminator' => 'Electricity',
'Light' => 'Electricity',
'Radioisotope' => 'Electricity',
'Battery' => 'Electricity',
'Ion' => 'Xenon',
'Xenon' => 'Xenon',
'Clamp-O-Tron' => 'Docking',
'Berthing' => 'Docking',
'RoveMax' => 'Rover Wheels'
);
// Science keywords => Science subcategory
$subcategoryKeywords['Science'] = array(
'Communotron' => 'Antennae',
'ometer' => 'Sensor',
'Detector' => 'Sensor'
);
// Mod prefix. Prefix is case sensitive
// Map of prefix to modname
$modPrefix = array(
'B9_' => 'B9',
'DEMV' => 'DEMV',
'DR' => 'DR',
'FS_' => 'FS',
'HL_' => 'HL',
'IEP_' => 'IEP',
'ISA_' => 'ISA',
'KAS_' => 'KAS',
'KW' => 'KW',
'kw' => 'KW',
'[LSI]' => 'LSI',
'MMI.K' => 'Kethane',
'kethane_' => 'Kethane',
'Engineer' => 'Engineer',
'protractor' => 'Protractor',
'quantum' => 'Quantum',
'strutGun' => 'Quantum',
'CBM-' => 'CBM',
'Romfarer_Lazor' => 'Lazor',
'Romfarer_RoboticArm' => 'RoboticArm',
'Kamony' => 'FusTekStationParts',
'RemoteTech_' => 'RemoteTech',
'HOME_' => 'HOME',
'Kosmos_' => 'Kosmos',
'cl_' => 'KSPX',
'NP_' => 'NP',
'dsm_' => 'DSM',
'mumech_' => 'MechJeb',
'crewManifest' => 'Crew_Manifest'
);
// Fallback order is by mod
$modOrder = array_flip(array('Stock', 'Engineer', 'DR', 'KAS', 'KW', 'IEP', 'Kethane', 'B9', 'HL', 'FS', 'ISA', 'HOME', 'Lazor', 'RoboticArm', 'CBM', 'Quantum', 'Protractor', 'Kosmos', 'FusTekStationParts', 'LSI'));
// List of stock parts
$stockParts = array_flip(array(
"AdvancedCanard",
"advSasModule",
"StandardCtrlSrf",
"airplaneTail",
"airScoop",
"asasmodule1-2",
"avionicsNoseCone",
"batteryBank",
"batteryPack",
"CanardController",
"CircularIntake",
"commDish",
"crewCabin",
"decoupler1-2",
"deltaWing",
"dockingPort1",
"dockingPort2",
"dockingPort3",
"dockingPortLateral",
"fuelLine",
"fuelTank",
"fuelTank_long",
"fuelTank1-2",
"fuelTank2-2",
"fuelTank3-2",
"fuelTank4-2",
"fuelTankSmall",
"ionEngine",
"JetEngine",
"ksp_r_largeBatteryPack",
"ladder1",
"landingLeg1",
"landingLeg1-2",
"largeAdapter",
"largeAdapter2",
"largeSolarPanel",
"launchClamp1",
"linearRcs",
"liquidEngine",
"liquidEngine1-2",
"liquidEngine2",
"liquidEngine2-2",
"liquidEngine3",
"longAntenna",
"Mark1Cockpit",
"Mark2Cockpit",
"mark3Cockpit",
"microEngine",
"miniFuelTank",
"miniLandingLeg",
"Mark1-2Pod",
"MK1Fuselage",
"Mk1FuselageStructural",
"mk1pod",
"mk2Fuselage",
"mk2LanderCabin",
"mk2SpacePlaneAdapter",
"mk3Fuselage",
"mk3spacePlaneAdapter",
"nacelleBody",
"noseCone",
"noseConeAdapter",
"nuclearEngine",
"parachuteSingle",
"parachuteDrogue",
"parachuteLarge",
"parachuteRadial",
"probeCoreCube",
"probeCoreOcto",
"probeCoreOcto2",
"probeCoreSphere",
"radialDecoupler",
"radialDecoupler1-2",
"radialDecoupler2",
"radialEngineBody",
"radialLiquidEngine1-2",
"radialRCSTank",
"ramAirIntake",
"RCSBlock",
"RCSFuelTank",
"RCSTank1-2",
"rocketNoseCone",
"roverBody",
"roverWheel1",
"roverWheel2",
"roverWheel3",
"rtg",
"sasModule",
"sensorAccelerometer",
"sensorBarometer",
"sensorGravimeter",
"sensorThermometer",
"sepMotor1",
"smallCtrlSrf",
"SmallGearBay",
"smallHardpoint",
"smallRadialEngine",
"solarPanels1",
"solarPanels2",
"solarPanels3",
"solarPanels4",
"solarPanels5",
"solidBooster",
"solidBooster1-1",
"spotLight1",
"spotLight2",
"stackBiCoupler",
"stackDecoupler",
"stackDecouplerMini",
"stackPoint1",
"stackSeparator",
"stackSeparatorBig",
"stackSeparatorMini",
"stackTriCoupler",
"standardNoseCone",
"stationHub",
"structuralIBeam1",
"structuralIBeam2",
"structuralIBeam3",
"structuralMiniNode",
"structuralPanel1",
"structuralPanel2",
"structuralPylon",
"structuralWing",
"strutConnector",
"strutCube",
"strutOcto",
"sweptWing",
"tailfin",
"telescopicLadder",
"telescopicLadderBay",
"toroidalAerospike",
"toroidalFuelTank",
"trussAdapter",
"trussPiece1x",
"trussPiece3x",
"turboFanEngine",
"wingConnector",
"winglet",
"R8winglet",
"winglet3",
"xenonTank"
));
// Return the name property from the cfg file
function readName($filename) {
$name = null;
$fp = fopen($filename, 'r');
while (($line = fgets($fp)) !== false) {
if (preg_match('/=/', $line)) {
$pos = strpos($line, '=');
$key = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos + 1));
if ($key === 'name') {
$name = $value;
break;
}
}
}
fclose($fp);
return $name;
}
// Generic cfg reader
function readCfg($filename) {
$stack = array();
$node = array();
$fp = fopen($filename, 'r');
if ($fp === false) {
die("Unable to read '$filename'");
}
$lineNo = 0;
while (($line = fgets($fp)) !== false) {
$lineNo++;
$line = trim($line);
if ($line === '') {
continue;
}
if (preg_match('|^//|', $line)) {
continue;
}
if (preg_match('/^\s*{/', $line)) {
$stack[] = $node;
$node = array('_section_' => $misc);
} elseif (preg_match('/^\s*}/', $line)) {
$type = $node['_section_'];
unset($node['_section_']);
$name = $node['name'];
$parent = array_pop($stack);
if (array_key_exists($type, $parent)) {
if (array_key_exists(0, $parent[$type])) {
$parent[$type][] = $node;
} else {
$existingNode = $parent[$type];
$parent[$type] = array($existingNode, $node);
}
} else {
$parent[$type] = $node;
}
$node = $parent;
} elseif (preg_match('/=/', $line)) {
$pos = strpos($line, '=');
$key = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos + 1));
if (array_key_exists($key, $node)) {
if (is_array($node[$key])) {
$node[$key][] = $value;
} else {
$existingValue = $node[$key];
$node[$key] = array($existingValue, $value);
}
} else {
$node[$key] = $value;
}
} else {
if (preg_match('/{$/', $line)) {
$misc = rtrim($line, '{');
$stack[] = $node;
$node = array('_section_' => $misc);
} else {
$misc = $line;
}
}
}
fclose($fp);
for ($i = 0, $n = count($stack); $i < $n; $i++) {
$type = $node['_section_'];
unset($node['_section_']);
$name = $node['name'];
$parent = array_pop($stack);
if (array_key_exists($type, $parent)) {
if (array_key_exists(0, $parent[$type])) {
$parent[$type][] = $node;
} else {
$existingNode = $parent[$type];
$parent[$type] = array($existingNode, $node);
}
} else {
$parent[$type] = $node;
}
$node = $parent;
}
return $node;
}
// Part reader
function readPart($filename, $folder) {
global $categoryNames, $stockParts, $modPrefix, $fuelLineParts, $subcategoryKeywords;
$cfg = readCfg($filename);
// Convert category number into category name
if (is_numeric($cfg['category'])) {
$cfg['category'] = $categoryNames[$cfg['category']];
}
//
// Collect information about part
//
$part = array(
'name' => $cfg['name'],
'title' => $cfg['title'],
'original' => $folder, // Original foldername
'folder' => $folder, // Current foldername
'category' => $cfg['category'],
'subcategory' => 'Unknown',
'mod' => 'Unknown'
);
// Define the mod of the part if possible
if (array_key_exists($part['name'], $stockParts)) {
$part['mod'] = 'Stock';
} else {
foreach ($modPrefix as $prefix => $modName) {
$test = substr($folder, 0, strlen($prefix));
if ($test === $prefix) {
$part['mod'] = $modName;
break;
}
}
}
if (preg_match('/\d\.\d+/', $part['title'], $matches)) {
$part['size'] = floatval($matches[0]);
} elseif (preg_match('/(\d)m/', $part['name'], $matches)) {
if ($matches[0] === '1m') {
$part['size'] = 1.25;
} elseif ($matches[0] === '2m') {
$part['size'] = 2.5;
} elseif ($matches[1] === '3m') {
$part['size'] = 3.75;
}
}
// Parse additional information based on category type of part
switch ($part['category']) {
case 'Pods':
// Pods have a vessel type: Probe, Lander, Ship, Station, etc
$part['vesselType'] = $cfg['vesselType'];
// Pods have a crew capacity
if (!empty($cfg['CrewCapacity'])) {
$part['crewCapacity'] = intval($cfg['CrewCapacity']);
} else {
// Probes usually have 0 crew capacity
$part['crewCapacity'] = 0;
}
break;
case 'Propulsion':
if (!empty($cfg['MODULE'])) {
// Search for ModuleEngines
$moduleEngines = null;
if (array_key_exists(0, $cfg['MODULE'])) {
foreach ($cfg['MODULE'] as $module) {
if (array_key_exists('name', $module) && $module['name'] === 'ModuleEngines') {
$moduleEngines = $module;
break;
}
}
} elseif (array_key_exists('name', $cfg['MODULE']) && $cfg['MODULE']['name'] === 'ModuleEngines') {
$moduleEngines = $cfg['MODULE'];
}
// If ModuleEngine exists and maxThrust property then the part is an engine
if ($moduleEngines !== null && array_key_exists('maxThrust', $moduleEngines)) {
$part['maxThrust'] = floatval($moduleEngines['maxThrust']);
$part['subcategory'] = 'Misc Engine';
// Check the propellants used by the engine
if (array_key_exists('PROPELLANT', $moduleEngines)) {
$propellants = $moduleEngines['PROPELLANT'];
if (!array_key_exists(0, $propellants)) {
$propellants = array($propellants);
}
// Convert propellant sections into list of propellant names
$propellantTypes = array();
foreach ($propellants as $propellant) {
$propellantTypes[] = $propellant['name'];
}
if (in_array('SolidFuel', $propellantTypes)) {
$part['subcategory'] = 'Solid Fuel Booster';
} elseif (in_array('IntakeAir', $propellantTypes)) {
$part['subcategory'] = 'Jet Engine';
} elseif (in_array('LiquidFuel', $propellantTypes) and in_array('Oxidizer', $propellantTypes)) {
$part['subcategory'] = 'Liquid Fuel Engine';
}
}
}
} elseif (!empty($cfg['RESOURCE'])) {
// Convert resource sections into associate array of resource name and the amount
$resources = array();
if (array_key_exists(0, $cfg['RESOURCE'])) {
foreach ($cfg['RESOURCE'] as $cfgResource) {
$resources[$cfgResource['name']] = floatval($cfgResource['maxAmount']);
}
} else {
$cfgResource = $cfg['RESOURCE'];
$resources[$cfgResource['name']] = floatval($cfgResource['maxAmount']);
}
// Determine type of fuel storage of part
$part['resources'] = $resources;
if (array_key_exists('LiquidFuel', $resources) and array_key_exists('Oxidizer', $resources)) {
$part['subcategory'] = 'Liquid Fuel Tank';
} elseif (array_key_exists('LiquidFuel', $resources)) {
$part['subcategory'] = 'Jet Fuel Tank';
} elseif (array_key_exists('MonoPropellant', $resources)) {
$part['subcategory'] = 'RCS';
} elseif (array_key_exists('Kethane', $resources)) {
$part['subcategory'] = 'Kethane Tank';
} elseif (array_key_exists('Oxidizer', $resources)) {
$part['subcategory'] = 'Oxidizer Tank';
} elseif (array_key_exists('IntakeAir', $resources)) {
$part['subcategory'] = 'Air Tank';
}
}
if (array_key_exists($part['name'], $fuelLineParts)) {
$part['subcategory'] = 'Fuel Line';
}
break;
case 'Control':
case 'Structural':
case 'Aero':
case 'Utility':
case 'Science':
foreach ($subcategoryKeywords[$part['category']] as $keyword => $subcategory) {
if (strpos($part['title'], $keyword) !== false) {
$part['subcategory'] = $subcategory;
break;
}
}
break;
}
return $part;
}
/**
* Used to sort all the parts
*/
function cmp_part($a, $b) {
global $categoryNames, $subcategoryOrder, $modOrder;
static $categoryOrder = null;
static $maxCategoryOrder, $maxModOrder;
static $maxSubcategoryOrder = array();
// Initalize
if ($categoryIndex === null) {
$categoryOrder = array_flip($categoryNames);
$categoryOrder['Pods'] = -1;
$maxCategoryOrder = count($categoryNames);
$maxModOrder = count($modOrder);
foreach ($categoryNames as $categoryName) {
$maxSubcategoryOrder[$categoryName] = array_key_exists($categoryName, $subcategoryOrder) ? count($subcategoryOrder[$categoryName]) : 0;
}
}
// Sort by category
$x = array_key_exists($a['category'], $categoryOrder) ? $categoryOrder[$a['category']] : $maxCategoryOrder;
$y = array_key_exists($b['category'], $categoryOrder) ? $categoryOrder[$b['category']] : $maxCategoryOrder;
if ($x !== $y) {
return $x - $y;
}
// Sort by subcategory
if (array_key_exists($a['category'], $subcategoryOrder)) {
$order = $subcategoryOrder[$a['category']];
$max = $maxSubcategoryOrder[$a['category']];
$x = array_key_exists($a['subcategory'], $order) ? $order[$a['subcategory']] : $max;
$y = array_key_exists($b['subcategory'], $order) ? $order[$b['subcategory']] : $max;
if ($x !== $y) {
return $x - $y;
}
}
// Sort parts inside a subcategory
if ($a['category'] === 'Pods') {
// Pods are not seperated by subcategory, inside we sort by crew capacity
// If crew capacity is missing then sort by vessel type
if (!isset($a['crewCapacity']) || !isset($b['crewCapacity'])) {
return strcmp($a['vesselType'], $b['vesselType']);
}
return $a['crewCapacity'] - $b['crewCapacity'];
} elseif ($a['category'] === 'Propulsion') {
// Handle sorting inside a subcategory
// Sort engines by thrust
if (isset($a['maxThrust']) and isset($b['maxThrust'])) {
return $a['maxThrust'] - $b['maxThrust'];
// Sort tanks by storage capacity
} elseif ($a['subcategory'] === 'Liquid Fuel Tank' || $a['subcategory'] === 'Jet Fuel Tank') {
return $a['resources']['LiquidFuel'] - $b['resources']['LiquidFuel'];
} elseif ($a['subcategory'] === 'RCS') {
return $a['resources']['MonoPropellant'] - $b['resources']['MonoPropellant'];
} elseif ($a['subcategory'] === 'Kethane Tank') {
return $a['resources']['Kethane'] - $b['resources']['Kethane'];
}
// Fall through
}
// Sort by modOrder
$x = array_key_exists($a['mod'], $modOrder) ? $modOrder[$a['mod']] : $maxModOrder;
$y = array_key_exists($b['mod'], $modOrder) ? $modOrder[$b['mod']] : $maxModOrder;
if ($x !== $y) {
return $x - $y;
}
// Sort by title of part
return strcmp($a['title'], $b['title']);
}
/**
* Returns the part suffix part of the new folder name for a part
*/
function getFoldername($part) {
$foldername = trim(preg_replace("/[^a-zA-Z0-9-]+/", '_', $part['title']), '_');
$modPrefix = $part['mod'] . '_';
if (substr($foldername, 0, strlen($modPrefix)) !== $modPrefix) {
$foldername = $modPrefix . $foldername;
}
if (isset($part['size'])) {
$foldername = str_replace('.', '', sprintf('%0.3f', $part['size'])) . 'mm_' . $foldername;
}
return $foldername;
}
/**
* Get the original folder names for the parts
*/
function getOriginalFolders() {
$json = file_get_contents('rename.json');
if ($json === false) {
die("Unable to read rename.json");
}
$parts = json_decode($json, true);
if ($parts === null) {
die("Invalid rename.json");
}
// Build associative array of part name => original foldername
$original = array();
foreach ($parts as $part) {
$original[$part['name']] = $part['original'];
}
return $original;
}
/**
* Rename the part folders
*/
function renameParts() {
global $categoryNames;
$parts = array();
// If parts have already been renamed then find out the original folder name
if (file_exists('rename.json')) {
$original = getOriginalFolders();
} else {
$original = array();
}
// Loop through the directories looking for part configuration files
foreach (glob("*/part.cfg") as $filename) {
$part = readPart($filename, dirname($filename));
// Restore the original foldername for the part
if (array_key_exists($part['name'], $original)) {
$part['original'] = $original[$part['name']];
}
$parts[] = $part;
}
// Sort the parts
usort($parts, 'cmp_part');
// Setup an order number for each category
$orderNo = array();
foreach ($categoryNames as $categoryName) {
$orderNo[$categoryName] = 0;
}
// Log details about the parts
foreach ($parts as &$part) {
if (!array_key_exists($part['category'], $orderNo)) {
$part['rename'] = $part['folder'];
continue;
}
$n = $orderNo[$part['category']]++;
$part['rename'] = $part['category'] . '_' . sprintf('%03d', $n) . '_' . getFoldername($part);
}
$fp = fopen('rename.json', 'w');
if ($fp === false) {
die("Unable to open rename.json for writing");
}
if (fwrite($fp, json_encode($parts)) === false) {
fclose($fp);
die("Unable to write rename.json");
}
fclose($fp);
// Rename the folders
foreach ($parts as $part) {
if (@rename($part['folder'], $part['rename']) === false) {
echo "WARNING: Unable to rename folder for part '" . $part['name'] . "'; " . $part['folder'] . ' -> ' . $part['rename'] . PHP_EOL;
}
}
}
/**
* Restore the part to its original folder name
*/
function restoreParts() {
$original = getOriginalFolders();
// Loop through the directories looking for part configuration files
foreach (glob("*/part.cfg") as $filename) {
$folder = dirname($filename);
$name = readName($filename); // Get the name of the part
if (array_key_exists($name, $original)) {
$originalFoldername = $original[$name]; // Original folder name
if ($folder != $originalFoldername) {
if (@rename($folder, $originalFoldername) === false) {
echo "WARNING: Unable to restore folder for part '" . $name . "'; $folder -> $originalFoldername" . PHP_EOL;
}
}
}
}
}
if ($argv[1] === '/rename') {
renameParts();
} elseif ($argv[1] === '/restore') {
restoreParts();
} else {
echo "USAGE: php ksp_rename.php " . PHP_EOL;
echo " /rename - Rename the part folder names" . PHP_EOL;
echo " /restore - Restore the part folder names" . PHP_EOL;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment