<commit_before>"""Data from Verlet's first paper of Lennard-Jones fluids.

       Computer "Experiments" on Clasical Fluids. I.
          Thermodynamical Properties of Lennard-Jones Molecules
       L. Verlet, Phys Rev. 159, 98 (1967)

   Copied from paper using OCR and therefore may include errors.
"""

import numpy as np

raw     = '''
0.88     1.095   3.48    -5.66   23.3   58.2   32.1   1.81   2.5
0.88     0.94    2.72    -5.84   21.4   52.4   29.8   1.76   2.5
0.88     0.591   -0.18   -6.53   17.3   42.5   26.2   1.62   2.5
0.85     2.889   4.36    -4.25   35.1   90.7   44.5   2.04   2.5
0.85     2.202   4.20    -4.76   30.9   77.4   39.3   1.97   2.5
0.85     1.214   3.06    -5.60   22.4   54.7   30.3   1.81   2.5
0.85     1.128   2.78    -5.69   21.5   52.2   29.3   1.78   2.5
0.85     0.880   1.64    -5.94   18.8   45.0   26.4   1.70   2.5
0.85     0.782   0.98    -6.04   17.8   42.1   25.3   1.66   2.5
0.85     0.786   0.99    -6.05   17.8   42.2   25.4   1.67   2.5
0.85     0.760   0.78    -6.07   17.5   41.4   25.0   1.66   2.5
0.85     0.719   0.36    -6.12   17.0   39.9   24.4   1.63   2.5
0.85     0.658   -0.20   -6.19   16.4   38.3   23.8   1.61   2.5
0.85     0.591   -1.20   -6.26   15.5   35.7   22.7   1.57   2.5
0.75     2.849   3.10    -4.07   26.8   59.3   30.2   1.96   2.5
0.75     1.304   1.61    -5.02   ...    36.1   20.9   1.73   2.5
0.75     1.069   0.90    -5.19   15.4   32.0   19.3   1.65   2.5
0.75     1.071   0.89    -5.17   15.4   31.6   19.1   1.66   3.3
0.75     0.881   -0.12   -5.31   14.1   27.8   17.5   1.56   2.5
0.75     0.827   -0.54   -5.38   13.2   26.9   17.2   1.53   2.5
0.65     2.557   2.14    -3.78   ...    35.8   19.3   1.85   3.3
0.65     1.585   1.25    -4.23   ...    25.5   15.0   1.70   2.5
0.65     1.036   -0.11   -4.52   11.5   19.7   12.7   1.55   3.3
0.65     0.900   -0.74   -4.61   10.4   17.8   11.9   1.50   2.5
0.55     2.645   1.63    -3.24   ...    23.0   12.7   1.80   2.5
0.5426   3.26    1.86    -3.00   ...    26.1   13.9   1.88   2.5
0.5426   1.404   0.57    -3.63   ...    14.2   9.0    1.58   2.5
0.5426   1.326   0.42    -3.66   ...    13.9   8.9    1.56   3.5
0.5      1.36    3.40    -3.38   8.5    11.4   7.4    1.54   2.3
0.45     4.625   1.68    -2.22   14.9   21.4   11.2   1.91   2.5
0.45     2.935   1.38    -2.60   11.0   14.8   8.3    1.79   2.5
0.45     1.744   0.74    -2.90   8.20   11.0   6.7    1.64   2.5
0.45     1.764   0.76    -2.89   8.20   11.0   6.7    1.64   2.5
0.45     1.710   0.74    -2.95   8.20   11.0   6.7    1.64   2.5
0.45     1.552   0.75    -2.98   7.7    10.1   6.4    1.58   2.5
0.4      1.462   0.41    -2.72   ...    7.27   4.77   1.53   2.5
0.4      1.424   0.38    -2.73   ...    7.22   4.75   1.52   3.3
0.35     1.620   0.58    -2.31   5.8    5.70   3.72   1.54   2.5
0.35     1.418   0.40    -2.21   ...    4.92   3.31   1.49   2.5
'''

fields = 'rho T virial U omega K G KG r_v'.split()
row_dtype = np.dtype([(field, '<f16') for field in fields])

entries = np.array(list(
  tuple(np.nan if el == '...' else float(el) for el in row)
  for row in (line.split() for line in (line.strip() for line in raw.split('\n'))
              if line)),
  dtype=row_dtype)
<commit_msg>Fixed spelling error<commit_after>"""Data from Verlet's first paper on Lennard-Jones fluids.

       Computer "Experiments" on Clasical Fluids. I.
          Thermodynamical Properties of Lennard-Jones Molecules
       L. Verlet, Phys Rev. 159, 98 (1967)

   Copied from paper using OCR and therefore may include errors.
"""

import numpy as np

raw     = '''
0.88     1.095   3.48    -5.66   23.3   58.2   32.1   1.81   2.5
0.88     0.94    2.72    -5.84   21.4   52.4   29.8   1.76   2.5
0.88     0.591   -0.18   -6.53   17.3   42.5   26.2   1.62   2.5
0.85     2.889   4.36    -4.25   35.1   90.7   44.5   2.04   2.5
0.85     2.202   4.20    -4.76   30.9   77.4   39.3   1.97   2.5
0.85     1.214   3.06    -5.60   22.4   54.7   30.3   1.81   2.5
0.85     1.128   2.78    -5.69   21.5   52.2   29.3   1.78   2.5
0.85     0.880   1.64    -5.94   18.8   45.0   26.4   1.70   2.5
0.85     0.782   0.98    -6.04   17.8   42.1   25.3   1.66   2.5
0.85     0.786   0.99    -6.05   17.8   42.2   25.4   1.67   2.5
0.85     0.760   0.78    -6.07   17.5   41.4   25.0   1.66   2.5
0.85     0.719   0.36    -6.12   17.0   39.9   24.4   1.63   2.5
0.85     0.658   -0.20   -6.19   16.4   38.3   23.8   1.61   2.5
0.85     0.591   -1.20   -6.26   15.5   35.7   22.7   1.57   2.5
0.75     2.849   3.10    -4.07   26.8   59.3   30.2   1.96   2.5
0.75     1.304   1.61    -5.02   ...    36.1   20.9   1.73   2.5
0.75     1.069   0.90    -5.19   15.4   32.0   19.3   1.65   2.5
0.75     1.071   0.89    -5.17   15.4   31.6   19.1   1.66   3.3
0.75     0.881   -0.12   -5.31   14.1   27.8   17.5   1.56   2.5
0.75     0.827   -0.54   -5.38   13.2   26.9   17.2   1.53   2.5
0.65     2.557   2.14    -3.78   ...    35.8   19.3   1.85   3.3
0.65     1.585   1.25    -4.23   ...    25.5   15.0   1.70   2.5
0.65     1.036   -0.11   -4.52   11.5   19.7   12.7   1.55   3.3
0.65     0.900   -0.74   -4.61   10.4   17.8   11.9   1.50   2.5
0.55     2.645   1.63    -3.24   ...    23.0   12.7   1.80   2.5
0.5426   3.26    1.86    -3.00   ...    26.1   13.9   1.88   2.5
0.5426   1.404   0.57    -3.63   ...    14.2   9.0    1.58   2.5
0.5426   1.326   0.42    -3.66   ...    13.9   8.9    1.56   3.5
0.5      1.36    3.40    -3.38   8.5    11.4   7.4    1.54   2.3
0.45     4.625   1.68    -2.22   14.9   21.4   11.2   1.91   2.5
0.45     2.935   1.38    -2.60   11.0   14.8   8.3    1.79   2.5
0.45     1.744   0.74    -2.90   8.20   11.0   6.7    1.64   2.5
0.45     1.764   0.76    -2.89   8.20   11.0   6.7    1.64   2.5
0.45     1.710   0.74    -2.95   8.20   11.0   6.7    1.64   2.5
0.45     1.552   0.75    -2.98   7.7    10.1   6.4    1.58   2.5
0.4      1.462   0.41    -2.72   ...    7.27   4.77   1.53   2.5
0.4      1.424   0.38    -2.73   ...    7.22   4.75   1.52   3.3
0.35     1.620   0.58    -2.31   5.8    5.70   3.72   1.54   2.5
0.35     1.418   0.40    -2.21   ...    4.92   3.31   1.49   2.5
'''

fields = 'rho T virial U omega K G KG r_v'.split()
row_dtype = np.dtype([(field, '<f16') for field in fields])

entries = np.array(list(
  tuple(np.nan if el == '...' else float(el) for el in row)
  for row in (line.split() for line in (line.strip() for line in raw.split('\n'))
              if line)),
  dtype=row_dtype)
<|endoftext|>


<commit_before>import os
import re

from KSPUtils import ConfigNode, NamedObject, Part, Module, Resource, SearchQuery, SearchTerm
from KSPUtils.Collections import ValueCollection

class TankType(NamedObject):
    def __init__(self):
        NamedObject.__init__(self)
        self.UnitsPerLiter = dict()

    def load(self, node):
        NamedObject.load(self, node)
        val = self.values.get('PossibleResources')
        if val:
            self.UnitsPerLiter = {r[0]: float(r[1]) for r in
                                  (res.strip().split() for res in val.value.split(';'))
                                  if len(r) > 1}

    @property
    def PossibleResources(self):
        return list(self.UnitsPerLiter.keys())

TankType.register('TANKTYPE')
TankType.mirror_value('UsefulVolumeRatio', float)
TankType.mirror_value('TankCostPerSurface', float)

class TanksLib(NamedObject): pass

TanksLib.setup_children_dict('types', 'TANKTYPE')

FLOAT_DECIMALS = 6

def round_float(val):
    return round(float(val), FLOAT_DECIMALS)

class ModuleTankManager(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleTankManager'

ModuleTankManager.mirror_value('Volume', round_float)
ModuleTankManager.mirror_value('DoCostPatch', bool)
ModuleTankManager.mirror_value('DoMassPatch', bool)
ModuleTankManager.mirror_value('IncludeTankTypes')
ModuleTankManager.mirror_value('ExcludeTankTypes')

class ModuleTank(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleSwitchableTank'

ModuleTank.mirror_value('Volume', round_float)
ModuleTank.mirror_value('InitialAmount', round_float)
ModuleTank.mirror_value('TankType')
ModuleTank.mirror_value('CurrentResource')
ModuleTank.mirror_value('ChooseTankType', bool)
ModuleTank.mirror_value('DoCostPatch', bool)
ModuleTank.mirror_value('DoMassPatch', bool)
ModuleTank.mirror_value('IncludeTankTypes')
ModuleTank.mirror_value('ExcludeTankTypes')

class Tank(NamedObject):
    type = 'TANK'

    def __init__(self, name=None):
        NamedObject.__init__(self)
        if name: self.name = name

Tank.mirror_value('Volume', round_float)
Tank.mirror_value('InitialAmount', round_float)
Tank.mirror_value('TankType')
Tank.mirror_value('CurrentResource')

class Patcher(object):
    def __init__(self, typelib, gamedata):
        self.game_data = gamedata
        self.tank_types_file = typelib
        self.tank_types = TanksLib.from_node(ConfigNode.Load(self.tank_types_file))
        self.types = self.tank_types.types
        self.part_filter = None

    def volume(self, tanktype, name, units):
        t = self.types[tanktype]
        upl = t.UnitsPerLiter.get(name)
        if not upl:
            print('WARNING: no UnitsPerLiter value for %s in %s:\n%s' % (name, tanktype, str(t)))
        return units / upl / t.UsefulVolumeRatio / 1e3 if upl else 0

    @staticmethod
    def get_parts(path):
        parts = []
        for dirpath, _dirnames, filenames in os.walk(path):
            for filename in filenames:
                if filename.endswith('.cfg'):
                    node = ConfigNode.Load(os.path.join(dirpath, filename))
                    if node.name == 'PART':
                        part = Part.from_node(node)
                        resources = part.resources
                        parts.append((part, resources))
        return parts

    @staticmethod
    def print_patches(stream, patches, header):
        stream.write(f'\n//{header}\n//Automatically generated using PyKSPutils library\n')
        stream.write('\n\n'.join(str(p) for p in patches))

    @staticmethod
    def add_patches(part, patch, addons):
        for term, addon in addons:
            if term.match(part):
                if isinstance(addon, ValueCollection.Value):
                    patch.AddValueItem(addon)
                elif isinstance(addon, NamedObject):
                    patch.AddChild(addon)

    def patch_1RES(self, stream, parts, tank_type, res_name,
                   monotype=None, addons=None, add_spec=''):
        patches = []
        rate = self.volume(tank_type, res_name, 1)
        polytype = lambda part: True
        if monotype:
            if isinstance(monotype, (list, tuple)):
                polytype = lambda part: part.name not in monotype
            elif monotype in ('all', 'strict'):
                polytype = lambda part: False
            elif isinstance(monotype, str):
                mono_re = re.compile(monotype)
                polytype = lambda part: mono_re.match(part.name) is None
        for part, resources in parts:
            if len(resources) == 1 and res_name in resources:
                if self.part_filter is not None and self.part_filter.match(part):
                    continue
                print('Patching %s' % part.name)
                res = resources[res_name]
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                V = res.maxAmount * rate
                ini = res.amount / res.maxAmount
                comment = f'{res.maxAmount} units of {res_name}: conversion rate is {rate} m3/u'
                patch.AddChild(Resource.Patch('!', res_name))
                can_change_type = polytype(part)
                if can_change_type or monotype != 'strict':
                    mgr = ModuleTankManager()
                    mgr.Volume = V
                    mgr.SetComment('Volume', comment)
                    mgr.DoCostPatch = True
                    mgr.DoMassPatch = True
                    if not can_change_type:
                        mgr.IncludeTankTypes = tank_type
                    tank = Tank()
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    tank.InitialAmount = ini
                    tank.Volume = 100
                    mgr.AddChild(tank)
                    patch.AddChild(mgr)
                else:
                    tank = ModuleTank()
                    tank.Volume = V
                    tank.SetComment('Volume', comment)
                    tank.InitialAmount = ini
                    tank.DoCostPatch = True
                    tank.DoMassPatch = True
                    tank.ChooseTankType = False
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    patch.AddChild(tank)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, '%s Tanks' % res_name)

    def patch_LFO(self, stream, parts, addons=None, add_spec=''):
        patches = []
        rate = self.volume('LiquidChemicals', 'LiquidFuel', 1) / 0.45
        for part, resources in parts:
            if len(resources) == 2 and 'LiquidFuel' in resources and 'Oxidizer' in resources:
                if self.part_filter is not None and self.part_filter.match(part):
                    continue
                print('Patching %s' % part.name)
                lf = resources['LiquidFuel']
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                patch.AddChild(Resource.Patch('!', 'LiquidFuel'))
                patch.AddChild(Resource.Patch('!', 'Oxidizer'))
                mgr = ModuleTankManager()
                mgr.Volume = lf.maxAmount * rate
                mgr.SetComment('Volume',
                               f'{lf.maxAmount} units of LF: conversion rate is {rate} m3/u')
                mgr.DoCostPatch = True
                mgr.DoMassPatch = True
                tank = Tank('LFO')
                tank.Volume = 100
                mgr.AddChild(tank)
                patch.AddChild(mgr)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, 'Rocket Fuel Tanks')

    def patch_parts(self, output, paths, addons=None, add_spec=''):
        for path in paths:
            parts = self.get_parts(os.path.join(self.game_data, *path))
            if not parts:
                print('No parts in: {}'.format('/'.join(path)))
                continue
            with open(os.path.join(self.game_data, *output), 'w') as out:
                out.write('//Configurable Containers patch for %s\n' % os.path.join(*path))
                self.patch_LFO(out, parts, addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'LiquidChemicals', 'LiquidFuel', '.*[Ww]ing.*',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'LiquidChemicals', 'MonoPropellant',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Gases', 'XenonGas', 'strict',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Gases', 'ArgonGas', 'strict',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Soil', 'Ore',
                                addons=addons, add_spec=add_spec)
                out.write('\n//:mode=c#:\n')
            print('%s done.\n' % os.path.join(*path))

    def patch_mod(self, mod, addons=None, add_spec=''):
        output = ('ConfigurableContainers', 'Parts', f'{mod}_Patch.cfg')
        path = [[mod]]
        add_spec += ':AFTER[%(mod)s]' % {'mod': mod}
        self.patch_parts(output, path, addons, add_spec)

    def patch_mods(self, *mods):
        for m in mods: self.patch_mod(m)

if __name__ == '__main__':
    patcher = Patcher('GameData/ConfigurableContainers/TankTypes.cfg',
                      '/home/storage/Games/KSP_linux/PluginsArchives/'
                      'Development/AT_KSP_Plugins/KSP-test/'
                      'KSP_test_1.7.0/GameData')

    patcher.part_filter = SearchQuery('PART/MODULE:.*Engines.*/')
    patcher.part_filter.Or('PART/MODULE:.*Converter.*/')
    patcher.part_filter.Or('PART/MODULE:.*Harvester.*/')
    patcher.part_filter.Or('PART/MODULE:.*Drill.*/')
    patcher.part_filter.Or('PART/MODULE:.*[Ff]uelSwitch/')
    patcher.part_filter.Or('PART/MODULE:.*[Rr]esourceSwitch/')
    patcher.part_filter.Or('PART/MODULE:ModuleB9PartSwitch/')

    xenon_titles = [
        (SearchTerm('name:xenonTank$'),
         Part.PatchValue('@', 'title', 'PB-X150 Pressurized Gas Container')),
        (SearchTerm('name:xenonTankLarge$'),
         Part.PatchValue('@', 'title', 'PB-X750 Pressurized Gas Container')),
        (SearchTerm('name:xenonTankRadial$'),
         Part.PatchValue('@', 'title', 'PB-X50R Pressurized Gas Container')),
    ]

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Squad_Patch.cfg'),
                        [('Squad', 'Parts')], xenon_titles)

    patcher.patch_mods('KWRocketry',
                       'Mk2Expansion',
                       'Mk3Expansion',
                       'SpaceY-Lifters',
                       'SpaceY-Expanded',
                       'FuelTanksPlus',
                       'ModRocketSys',
                       'NearFuturePropulsion',
                       'SPS',  # Standard Propulsion Systems
                       'RaginCaucasian',  # Mk2.5 spaceplane parts
                       'MunarIndustries',  # Fuel Tank Expansion
                       'Bluedog_DB',  # Bluedog Design Bureau
                       'StreamlineEnginesTanks',
                       # 'UniversalStorage2',  # uses USFuelSwitch
                       'MiningExpansion',
                       # 'StationPartsExpansionRedux',  # uses B9PartSwitch
                       # 'ReStock', # no parts
                       'ReStockPlus',
                       'PlanetaryBaseInc',  # Kerbal Planetary Base Systems
                       # 'FelineUtilityRover',  # uses ModuleKerbetrotterResourceSwitch
                       )

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Tal-Tanks_Patch.cfg'),
                        [('ModsByTal', 'Parts')],
                        [(SearchTerm(''), Module.Patch('!', 'ModuleFuelTanks'))],
                        add_spec=':AFTER[ModsByTal]')

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'MakingHistory_Patch.cfg'),
                        [['ExpansionFuelTanks']])

    # USI uses FSfuelSwitch, so no patching for it
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-MKS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'Akita'),
    #                      ('UmbraSpaceIndustries', 'Konstruction'),
    #                      ('UmbraSpaceIndustries', 'Kontainers'),
    #                      ('UmbraSpaceIndustries', 'MKS'),
    #                      ('UmbraSpaceIndustries', 'ReactorPack'),
    #                     ],
    #                     add_spec=':NEEDS[MKS]:AFTER[MKS]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-LS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'LifeSupport'),
    #                      ],
    #                     add_spec=':NEEDS[USILifeSupport]:AFTER[USILifeSupport]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-FTT_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'FTT'),
    #                      ],
    #                     add_spec=':NEEDS[FTT]:AFTER[FTT]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-ExpPack_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'ExpPack'),
    #                      ],
    #                     add_spec=':NEEDS[ExpPack]:AFTER[ExpPack]')

    print('Done')
<commit_msg>Limiting conversion rate to 6 decimals in comments<commit_after>import os
import re

from KSPUtils import ConfigNode, NamedObject, Part, Module, Resource, SearchQuery, SearchTerm
from KSPUtils.Collections import ValueCollection

class TankType(NamedObject):
    def __init__(self):
        NamedObject.__init__(self)
        self.UnitsPerLiter = dict()

    def load(self, node):
        NamedObject.load(self, node)
        val = self.values.get('PossibleResources')
        if val:
            self.UnitsPerLiter = {r[0]: float(r[1]) for r in
                                  (res.strip().split() for res in val.value.split(';'))
                                  if len(r) > 1}

    @property
    def PossibleResources(self):
        return list(self.UnitsPerLiter.keys())

TankType.register('TANKTYPE')
TankType.mirror_value('UsefulVolumeRatio', float)
TankType.mirror_value('TankCostPerSurface', float)

class TanksLib(NamedObject): pass

TanksLib.setup_children_dict('types', 'TANKTYPE')

FLOAT_DECIMALS = 6

def round_float(val):
    return round(float(val), FLOAT_DECIMALS)

class ModuleTankManager(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleTankManager'

ModuleTankManager.mirror_value('Volume', round_float)
ModuleTankManager.mirror_value('DoCostPatch', bool)
ModuleTankManager.mirror_value('DoMassPatch', bool)
ModuleTankManager.mirror_value('IncludeTankTypes')
ModuleTankManager.mirror_value('ExcludeTankTypes')

class ModuleTank(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleSwitchableTank'

ModuleTank.mirror_value('Volume', round_float)
ModuleTank.mirror_value('InitialAmount', round_float)
ModuleTank.mirror_value('TankType')
ModuleTank.mirror_value('CurrentResource')
ModuleTank.mirror_value('ChooseTankType', bool)
ModuleTank.mirror_value('DoCostPatch', bool)
ModuleTank.mirror_value('DoMassPatch', bool)
ModuleTank.mirror_value('IncludeTankTypes')
ModuleTank.mirror_value('ExcludeTankTypes')

class Tank(NamedObject):
    type = 'TANK'

    def __init__(self, name=None):
        NamedObject.__init__(self)
        if name: self.name = name

Tank.mirror_value('Volume', round_float)
Tank.mirror_value('InitialAmount', round_float)
Tank.mirror_value('TankType')
Tank.mirror_value('CurrentResource')

class Patcher(object):
    def __init__(self, typelib, gamedata):
        self.game_data = gamedata
        self.tank_types_file = typelib
        self.tank_types = TanksLib.from_node(ConfigNode.Load(self.tank_types_file))
        self.types = self.tank_types.types
        self.part_filter = None

    def volume(self, tanktype, name, units):
        t = self.types[tanktype]
        upl = t.UnitsPerLiter.get(name)
        if not upl:
            print('WARNING: no UnitsPerLiter value for %s in %s:\n%s' % (name, tanktype, str(t)))
        return units / upl / t.UsefulVolumeRatio / 1e3 if upl else 0

    @staticmethod
    def get_parts(path):
        parts = []
        for dirpath, _dirnames, filenames in os.walk(path):
            for filename in filenames:
                if filename.endswith('.cfg'):
                    node = ConfigNode.Load(os.path.join(dirpath, filename))
                    if node.name == 'PART':
                        part = Part.from_node(node)
                        resources = part.resources
                        parts.append((part, resources))
        return parts

    @staticmethod
    def print_patches(stream, patches, header):
        stream.write(f'\n//{header}\n//Automatically generated using PyKSPutils library\n')
        stream.write('\n\n'.join(str(p) for p in patches))

    @staticmethod
    def add_patches(part, patch, addons):
        for term, addon in addons:
            if term.match(part):
                if isinstance(addon, ValueCollection.Value):
                    patch.AddValueItem(addon)
                elif isinstance(addon, NamedObject):
                    patch.AddChild(addon)

    def patch_1RES(self, stream, parts, tank_type, res_name,
                   monotype=None, addons=None, add_spec=''):
        patches = []
        rate = self.volume(tank_type, res_name, 1)
        polytype = lambda part: True
        if monotype:
            if isinstance(monotype, (list, tuple)):
                polytype = lambda part: part.name not in monotype
            elif monotype in ('all', 'strict'):
                polytype = lambda part: False
            elif isinstance(monotype, str):
                mono_re = re.compile(monotype)
                polytype = lambda part: mono_re.match(part.name) is None
        for part, resources in parts:
            if len(resources) == 1 and res_name in resources:
                if self.part_filter is not None and self.part_filter.match(part):
                    continue
                print('Patching %s' % part.name)
                res = resources[res_name]
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                V = res.maxAmount * rate
                ini = res.amount / res.maxAmount
                comment = f'{res.maxAmount} units of {res_name}: conversion rate is {rate:.6f} m3/u'
                patch.AddChild(Resource.Patch('!', res_name))
                can_change_type = polytype(part)
                if can_change_type or monotype != 'strict':
                    mgr = ModuleTankManager()
                    mgr.Volume = V
                    mgr.SetComment('Volume', comment)
                    mgr.DoCostPatch = True
                    mgr.DoMassPatch = True
                    if not can_change_type:
                        mgr.IncludeTankTypes = tank_type
                    tank = Tank()
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    tank.InitialAmount = ini
                    tank.Volume = 100
                    mgr.AddChild(tank)
                    patch.AddChild(mgr)
                else:
                    tank = ModuleTank()
                    tank.Volume = V
                    tank.SetComment('Volume', comment)
                    tank.InitialAmount = ini
                    tank.DoCostPatch = True
                    tank.DoMassPatch = True
                    tank.ChooseTankType = False
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    patch.AddChild(tank)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, '%s Tanks' % res_name)

    def patch_LFO(self, stream, parts, addons=None, add_spec=''):
        patches = []
        rate = self.volume('LiquidChemicals', 'LiquidFuel', 1) / 0.45
        for part, resources in parts:
            if len(resources) == 2 and 'LiquidFuel' in resources and 'Oxidizer' in resources:
                if self.part_filter is not None and self.part_filter.match(part):
                    continue
                print('Patching %s' % part.name)
                lf = resources['LiquidFuel']
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                patch.AddChild(Resource.Patch('!', 'LiquidFuel'))
                patch.AddChild(Resource.Patch('!', 'Oxidizer'))
                mgr = ModuleTankManager()
                mgr.Volume = lf.maxAmount * rate
                mgr.SetComment('Volume',
                               f'{lf.maxAmount} units of LF: conversion rate is {rate:.6f} m3/u')
                mgr.DoCostPatch = True
                mgr.DoMassPatch = True
                tank = Tank('LFO')
                tank.Volume = 100
                mgr.AddChild(tank)
                patch.AddChild(mgr)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, 'Rocket Fuel Tanks')

    def patch_parts(self, output, paths, addons=None, add_spec=''):
        for path in paths:
            parts = self.get_parts(os.path.join(self.game_data, *path))
            if not parts:
                print('No parts in: {}'.format('/'.join(path)))
                continue
            with open(os.path.join(self.game_data, *output), 'w') as out:
                out.write('//Configurable Containers patch for %s\n' % os.path.join(*path))
                self.patch_LFO(out, parts, addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'LiquidChemicals', 'LiquidFuel', '.*[Ww]ing.*',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'LiquidChemicals', 'MonoPropellant',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Gases', 'XenonGas', 'strict',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Gases', 'ArgonGas', 'strict',
                                addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts,
                                'Soil', 'Ore',
                                addons=addons, add_spec=add_spec)
                out.write('\n//:mode=c#:\n')
            print('%s done.\n' % os.path.join(*path))

    def patch_mod(self, mod, addons=None, add_spec=''):
        output = ('ConfigurableContainers', 'Parts', f'{mod}_Patch.cfg')
        path = [[mod]]
        add_spec += ':AFTER[%(mod)s]' % {'mod': mod}
        self.patch_parts(output, path, addons, add_spec)

    def patch_mods(self, *mods):
        for m in mods: self.patch_mod(m)

if __name__ == '__main__':
    patcher = Patcher('GameData/ConfigurableContainers/TankTypes.cfg',
                      '/home/storage/Games/KSP_linux/PluginsArchives/'
                      'Development/AT_KSP_Plugins/KSP-test/'
                      'KSP_test_1.7.0/GameData')

    patcher.part_filter = SearchQuery('PART/MODULE:.*Engines.*/')
    patcher.part_filter.Or('PART/MODULE:.*Converter.*/')
    patcher.part_filter.Or('PART/MODULE:.*Harvester.*/')
    patcher.part_filter.Or('PART/MODULE:.*Drill.*/')
    patcher.part_filter.Or('PART/MODULE:.*[Ff]uelSwitch/')
    patcher.part_filter.Or('PART/MODULE:.*[Rr]esourceSwitch/')
    patcher.part_filter.Or('PART/MODULE:ModuleB9PartSwitch/')

    xenon_titles = [
        (SearchTerm('name:xenonTank$'),
         Part.PatchValue('@', 'title', 'PB-X150 Pressurized Gas Container')),
        (SearchTerm('name:xenonTankLarge$'),
         Part.PatchValue('@', 'title', 'PB-X750 Pressurized Gas Container')),
        (SearchTerm('name:xenonTankRadial$'),
         Part.PatchValue('@', 'title', 'PB-X50R Pressurized Gas Container')),
    ]

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Squad_Patch.cfg'),
                        [('Squad', 'Parts')], xenon_titles)

    patcher.patch_mods('KWRocketry',
                       'Mk2Expansion',
                       'Mk3Expansion',
                       'SpaceY-Lifters',
                       'SpaceY-Expanded',
                       'FuelTanksPlus',
                       'ModRocketSys',
                       'NearFuturePropulsion',
                       'SPS',  # Standard Propulsion Systems
                       'RaginCaucasian',  # Mk2.5 spaceplane parts
                       'MunarIndustries',  # Fuel Tank Expansion
                       'Bluedog_DB',  # Bluedog Design Bureau
                       'StreamlineEnginesTanks',
                       # 'UniversalStorage2',  # uses USFuelSwitch
                       'MiningExpansion',
                       # 'StationPartsExpansionRedux',  # uses B9PartSwitch
                       # 'ReStock', # no parts
                       'ReStockPlus',
                       'PlanetaryBaseInc',  # Kerbal Planetary Base Systems
                       # 'FelineUtilityRover',  # uses ModuleKerbetrotterResourceSwitch
                       )

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Tal-Tanks_Patch.cfg'),
                        [('ModsByTal', 'Parts')],
                        [(SearchTerm(''), Module.Patch('!', 'ModuleFuelTanks'))],
                        add_spec=':AFTER[ModsByTal]')

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'MakingHistory_Patch.cfg'),
                        [['ExpansionFuelTanks']])

    # USI uses FSfuelSwitch, so no patching for it
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-MKS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'Akita'),
    #                      ('UmbraSpaceIndustries', 'Konstruction'),
    #                      ('UmbraSpaceIndustries', 'Kontainers'),
    #                      ('UmbraSpaceIndustries', 'MKS'),
    #                      ('UmbraSpaceIndustries', 'ReactorPack'),
    #                     ],
    #                     add_spec=':NEEDS[MKS]:AFTER[MKS]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-LS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'LifeSupport'),
    #                      ],
    #                     add_spec=':NEEDS[USILifeSupport]:AFTER[USILifeSupport]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-FTT_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'FTT'),
    #                      ],
    #                     add_spec=':NEEDS[FTT]:AFTER[FTT]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-ExpPack_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'ExpPack'),
    #                      ],
    #                     add_spec=':NEEDS[ExpPack]:AFTER[ExpPack]')

    print('Done')
<|endoftext|>


<commit_before>#!/usr/bin/python

import argparse
import compileall
import distutils.version
import glob
import json
import os
import plistlib
import shutil
import subprocess
import sys
import tempfile
import urllib
import webbrowser

def update_bundle_version(bundle_path, version, repo):
    info_plist_path = os.path.join(bundle_path, 'Contents', 'Info.plist')
    info_plist = plistlib.readPlist(info_plist_path)
    info_plist['CFBundleVersion'] = version
    info_plist['LBDescription']['LBDownloadURL'] = expand_url_template(
        'https://github.com/%s/releases/download/%s/%s-%s.zip', repo,
        tag_for_version(version), repo.split('/', 1)[1], version)

    plistlib.writePlist(info_plist, info_plist_path)

def sign_bundle(bundle_path):
    subprocess.check_call(['/usr/bin/codesign', '-fs', 'Developer ID Application: Nicholas Riley',
                           bundle_path])

PROJECT_PATH = None
def project_path():
    global PROJECT_PATH
    if PROJECT_PATH is None:
        PROJECT_PATH = subprocess.check_output(['/usr/bin/git', '-C', os.path.dirname(__file__),
                                                'rev-parse', '--show-toplevel']).rstrip('\n')
    return PROJECT_PATH

def git(*args):
    return subprocess.check_output(['/usr/bin/git', '-C', project_path()] + list(args))

def archive_bundles(product_name, version, bundle_paths):
    file_paths = git('ls-files', '-z', *bundle_paths).rstrip('\0').split('\0')

    git('commit', '-am', version)

    temp_dir_path = tempfile.mkdtemp(prefix='upload_' + product_name)
    archive_dir_path = os.path.join(temp_dir_path, '%s %s' % (product_name, version), '')
    git('checkout-index', '--prefix=' + archive_dir_path, *file_paths)
    git('reset', 'HEAD~')

    for py_path in (f for f in file_paths if f.endswith('.py')):
        py_path = os.path.join(archive_dir_path, py_path)
        compileall.compile_file(py_path)
        # can't delete default.py because LaunchBar won't let me point at a .pyc
        if os.path.split(py_path)[1] != 'default.py':
            os.unlink(py_path)

    for bundle_path in bundle_paths:
        sign_bundle(os.path.join(archive_dir_path, bundle_path))

    # note: a single action can be zipped into a .lbaction file instead
    archive_path = os.path.join(project_path(), '%s-%s.zip' % (product_name, version))
    subprocess.check_call(['/usr/bin/ditto', '-ck', temp_dir_path, archive_path])
    shutil.rmtree(temp_dir_path)

    return archive_path

def expand_url_template(url_template, *args, **query):
    url = url_template
    if args:
        url = url % tuple(map(urllib.quote, args))
    if query:
        url += '?' + urllib.urlencode(query)
    return url

def tag_for_version(version):
    return 'v' + version

def upload_release(repo, version, archive_path, github_access_token):
    strict_version = distutils.version.StrictVersion(version)

    releases_url = expand_url_template(
        'https://api.github.com/repos/%s/releases', repo,
        access_token=github_access_token)

    release_name = tag_for_version(version)
    release_json = dict(tag_name=release_name, target_commitish='master',
                        name=release_name, body='', draft=True,
                        prerelease=bool(strict_version.prerelease))

    print releases_url

    releases_api = subprocess.Popen(['/usr/bin/curl', '--data', '@-', releases_url],
                                    stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    release_json_data, _ = releases_api.communicate(json.dumps(release_json))
    release_json = json.loads(release_json_data)

    html_url = release_json['html_url']
    upload_url = release_json['upload_url'].split('{', 1)[0]
    upload_url = expand_url_template(upload_url,
        name=os.path.basename(archive_path), access_token=github_access_token)
    subprocess.check_call(
        ['/usr/bin/curl', '-H', 'Content-Type: application/zip',
         '--data-binary', '@' + archive_path, upload_url])

    return html_url

def release(version, github_access_token):
    repo = 'nriley/LBOfficeMRU'
    os.chdir(project_path())
    action_paths = glob.glob('*.lbaction')

    for action_path in action_paths:
        # update version number and download URL in the working copy so it can be committed
        update_bundle_version(action_path, version, repo)

    archive_path = archive_bundles('LBOfficeMRU', version, action_paths)

    if github_access_token is None:
        return

    html_url = upload_release('nriley/LBOfficeMRU', version, archive_path, github_access_token)
    webbrowser.open(html_url)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Build and optionally release to GitHub.')
    parser.add_argument('version')
    parser.add_argument('github_access_token', nargs='?', default=None)

    args = parser.parse_args()

    release(args.version, args.github_access_token)
<commit_msg>Attempt to avoid release oops in the future.<commit_after>#!/usr/bin/python

import argparse
import compileall
import distutils.version
import glob
import json
import os
import plistlib
import shutil
import subprocess
import sys
import tempfile
import urllib
import webbrowser

def update_bundle_version(bundle_path, version, repo):
    info_plist_path = os.path.join(bundle_path, 'Contents', 'Info.plist')
    info_plist = plistlib.readPlist(info_plist_path)
    info_plist['CFBundleVersion'] = version
    info_plist['LBDescription']['LBDownloadURL'] = expand_url_template(
        'https://github.com/%s/releases/download/%s/%s-%s.zip', repo,
        tag_for_version(version), repo.split('/', 1)[1], version)

    plistlib.writePlist(info_plist, info_plist_path)

def sign_bundle(bundle_path):
    subprocess.check_call(['/usr/bin/codesign', '-fs', 'Developer ID Application: Nicholas Riley',
                           bundle_path])

PROJECT_PATH = None
def project_path():
    global PROJECT_PATH
    if PROJECT_PATH is None:
        PROJECT_PATH = subprocess.check_output(['/usr/bin/git', '-C', os.path.dirname(__file__),
                                                'rev-parse', '--show-toplevel']).rstrip('\n')
    return PROJECT_PATH

def git(*args):
    return subprocess.check_output(['/usr/bin/git', '-C', project_path()] + list(args))

def archive_bundles(product_name, version, bundle_paths):
    file_paths = git('ls-files', '-z', *bundle_paths).rstrip('\0').split('\0')

    git('commit', '-am', version)

    temp_dir_path = tempfile.mkdtemp(prefix='upload_' + product_name)
    archive_dir_path = os.path.join(temp_dir_path, '%s %s' % (product_name, version), '')
    git('checkout-index', '--prefix=' + archive_dir_path, *file_paths)
    git('reset', 'HEAD~')

    for py_path in (f for f in file_paths if f.endswith('.py')):
        py_path = os.path.join(archive_dir_path, py_path)
        compileall.compile_file(py_path)
        # can't delete default.py because LaunchBar won't let me point at a .pyc
        if os.path.split(py_path)[1] != 'default.py':
            os.unlink(py_path)

    for bundle_path in bundle_paths:
        sign_bundle(os.path.join(archive_dir_path, bundle_path))

    # note: a single action can be zipped into a .lbaction file instead
    archive_path = os.path.join(project_path(), '%s-%s.zip' % (product_name, version))
    subprocess.check_call(['/usr/bin/ditto', '-ck', temp_dir_path, archive_path])
    shutil.rmtree(temp_dir_path)

    return archive_path

def expand_url_template(url_template, *args, **query):
    url = url_template
    if args:
        url = url % tuple(map(urllib.quote, args))
    if query:
        url += '?' + urllib.urlencode(query)
    return url

def tag_for_version(version):
    return 'v' + version

def upload_release(repo, version, archive_path, github_access_token):
    strict_version = distutils.version.StrictVersion(version)

    releases_url = expand_url_template(
        'https://api.github.com/repos/%s/releases', repo,
        access_token=github_access_token)

    release_name = tag_for_version(version)
    release_json = dict(tag_name=release_name, target_commitish='master',
                        name=release_name, body='', draft=True,
                        prerelease=bool(strict_version.prerelease))

    print releases_url

    releases_api = subprocess.Popen(['/usr/bin/curl', '--data', '@-', releases_url],
                                    stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    release_json_data, _ = releases_api.communicate(json.dumps(release_json))
    release_json = json.loads(release_json_data)

    html_url = release_json['html_url']
    upload_url = release_json['upload_url'].split('{', 1)[0]
    upload_url = expand_url_template(upload_url,
        name=os.path.basename(archive_path), access_token=github_access_token)
    subprocess.check_call(
        ['/usr/bin/curl', '-H', 'Content-Type: application/zip',
         '--data-binary', '@' + archive_path, upload_url])

    return html_url

def release(version, github_access_token):
    repo = 'nriley/LBOfficeMRU'
    os.chdir(project_path())
    action_paths = glob.glob('*.lbaction')

    for action_path in action_paths:
        # update version number and download URL in the working copy so it can be committed
        update_bundle_version(action_path, version, repo)

    archive_path = archive_bundles('LBOfficeMRU', version, action_paths)

    if github_access_token is None:
        return

    html_url = upload_release('nriley/LBOfficeMRU', version, archive_path, github_access_token)
    webbrowser.open(html_url)

    print "Make sure changes are committed and pushed before saving release as final!"

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Build and optionally release to GitHub.')
    parser.add_argument('version')
    parser.add_argument('github_access_token', nargs='?', default=None)

    args = parser.parse_args()

    release(args.version, args.github_access_token)
<|endoftext|>


<commit_before>
import os, re
from KSPUtils import ConfigNode, NamedObject, Part, Module, Resource, SearchQuery, SearchTerm
from KSPUtils.Collections import ValueCollection

class TankType(NamedObject):
    def __init__(self):
        NamedObject.__init__(self)
        self.UnitsPerLiter = dict()

    def load(self, node):
        NamedObject.load(self, node)
        val = self.values.get('PossibleResources')
        if val:
            self.UnitsPerLiter = dict((r[0], float(r[1])) for r in
                                      (res.strip().split() for res in val.value.split(';'))
                                      if len(r) > 1)

    @property
    def PossibleResources(self): return self.UnitsPerLiter.keys()
TankType.register('TANKTYPE')
TankType.mirror_value('UsefulVolumeRatio', float)
TankType.mirror_value('TankCostPerSurface', float)

class TanksLib(NamedObject): pass
TanksLib.setup_children_dict('types', 'TANKTYPE')

class ModuleTankManager(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleTankManager'
ModuleTankManager.mirror_value('Volume', float)
ModuleTankManager.mirror_value('DoCostPatch', bool)
ModuleTankManager.mirror_value('DoMassPatch', bool)
ModuleTankManager.mirror_value('IncludeTankTypes')
ModuleTankManager.mirror_value('ExcludeTankTypes')

class ModuleTank(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleSwitchableTank'
ModuleTank.mirror_value('Volume', float)
ModuleTank.mirror_value('InitialAmount', float)
ModuleTank.mirror_value('TankType')
ModuleTank.mirror_value('CurrentResource')
ModuleTank.mirror_value('ChooseTankType', bool)
ModuleTank.mirror_value('DoCostPatch', bool)
ModuleTank.mirror_value('DoMassPatch', bool)
ModuleTank.mirror_value('IncludeTankTypes')
ModuleTank.mirror_value('ExcludeTankTypes')

class Tank(NamedObject):
    type = 'TANK'
    def __init__(self, name=None):
        NamedObject.__init__(self)
        if name: self.name = name
Tank.mirror_value('Volume', float)
Tank.mirror_value('InitialAmount', float)
Tank.mirror_value('TankType')
Tank.mirror_value('CurrentResource')

class Patcher(object):
    def __init__(self, typelib, gamedata):
        self.game_data = gamedata
        self.tank_types_file = typelib
        self.tank_types = TanksLib.from_node(ConfigNode.Load(self.tank_types_file))
        self.types = self.tank_types.types
        self.part_filter = None

    def volume(self, tanktype, name, units):
        t = self.types[tanktype]
        upl = t.UnitsPerLiter.get(name)
        if not upl:
            print('WARNING: no UnitsPerLiter value for %s in %s:\n%s' % (name, tanktype, str(t)))
        return units / upl / t.UsefulVolumeRatio / 1e3 if upl else 0

    @staticmethod
    def get_parts(path):
        parts = []
        for dirpath, _dirnames, filenames in os.walk(path):
            for filename in filenames:
                if not filename.endswith('.cfg'): continue
                node = ConfigNode.Load(os.path.join(dirpath, filename))
                if node.name != 'PART': continue
                part = Part.from_node(node)
                resources = part.resources
                parts.append((part, resources))
        return parts

    @staticmethod
    def print_patches(stream, patches, header):
        stream.write('\n//%s\n//Automatically generated using PyKSPutils library\n' % header)
        stream.write('\n\n'.join(str(p) for p in patches))

    @staticmethod
    def add_patches(part, patch, addons):
        for term, addon in addons:
            if term.match(part):
                if isinstance(addon, ValueCollection.Value):
                    patch.AddValueItem(addon)
                elif isinstance(addon, NamedObject):
                    patch.AddChild(addon)

    def patch_1RES(self, stream, parts, tank_type, res_name, monotype=None, addons=None, add_spec=''):
        patches = []
        rate = self.volume(tank_type, res_name, 1)
        polytype = lambda part: True
        if monotype:
            if isinstance(monotype, (list, tuple)):
                polytype = lambda part: part.name not in monotype
            elif monotype in ('all', 'strict'):
                polytype = lambda part: False
            elif isinstance(monotype, str):
                mono_re = re.compile(monotype)
                polytype = lambda part: mono_re.match(part.name) is None
        for part, resources in parts:
            if len(resources) == 1 and res_name in resources:
                if self.part_filter is not None and self.part_filter.match(part): continue
                print('Patching %s' % part.name)
                res = resources[res_name]
                patch = Part.Patch('@', part.name,
                                   ':FOR[ConfigurableContainers]'
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                V = res.maxAmount * rate
                ini = res.amount / res.maxAmount
                comment = '%f units of %s: conversion rate is %f m3/u' % (res.maxAmount, res_name, rate)
                patch.AddChild(Resource.Patch('!', res_name))
                can_change_type = polytype(part)
                if can_change_type or monotype != 'strict':
                    mgr = ModuleTankManager()
                    mgr.Volume = V
                    mgr.SetComment('Volume', comment)
                    mgr.DoCostPatch = True
                    mgr.DoMassPatch = True
                    if not can_change_type:
                        mgr.IncludeTankTypes = tank_type
                    tank = Tank()
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    tank.InitialAmount = ini
                    tank.Volume = 100
                    mgr.AddChild(tank)
                    patch.AddChild(mgr)
                else:
                    tank = ModuleTank()
                    tank.Volume = V
                    tank.SetComment('Volume', comment)
                    tank.InitialAmount = ini
                    tank.DoCostPatch = True
                    tank.DoMassPatch = True
                    tank.ChooseTankType = False
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    patch.AddChild(tank)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, '%s Tanks' % res_name)

    def patch_LFO(self, stream, parts, addons=None, add_spec=''):
        patches = []
        rate = self.volume('LiquidChemicals', 'LiquidFuel', 1) / 0.45
        for part, resources in parts:
            if len(resources) == 2 and 'LiquidFuel' in resources and 'Oxidizer' in resources:
                if self.part_filter is not None and self.part_filter.match(part): continue
                print('Patching %s' % part.name)
                lf = resources['LiquidFuel']
                patch = Part.Patch('@', part.name,
                                   ':FOR[ConfigurableContainers]'
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                patch.AddChild(Resource.Patch('!', 'LiquidFuel'))
                patch.AddChild(Resource.Patch('!', 'Oxidizer'))
                mgr = ModuleTankManager()
                mgr.Volume = lf.maxAmount * rate
                mgr.SetComment('Volume', '%f units of LF: conversion rate is %f m3/u' % (lf.maxAmount, rate))
                mgr.DoCostPatch = True
                mgr.DoMassPatch = True
                tank = Tank('LFO')
                tank.Volume = 100
                mgr.AddChild(tank)
                patch.AddChild(mgr)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, 'Rocket Fuel Tanks')

    def patch_parts(self, output, paths, addons=None, add_spec=''):
        for path in paths:
            parts = self.get_parts(os.path.join(self.game_data, *path))
            if not parts: continue
            with open(os.path.join(self.game_data, *output), 'w') as out:
                out.write('//Configurable Containers patch for %s\n' % os.path.join(*path))
                self.patch_LFO(out, parts, addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'LiquidChemicals', 'LiquidFuel', '.*[Ww]ing.*', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'LiquidChemicals', 'MonoPropellant', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Gases', 'XenonGas', 'strict', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Gases', 'ArgonGas', 'strict', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Soil', 'Ore', addons=addons, add_spec=add_spec)
                out.write('\n//:mode=c#:\n')
            print('%s done.\n' % os.path.join(*path))

    def patch_mod(self, mod, addons=None, add_spec=''):
        output = ('ConfigurableContainers', 'Parts', '%s_Patch.cfg' % mod)
        path = [[mod]]
        add_spec += ':NEEDS[%(mod)s]:AFTER[%(mod)s]' % {'mod': mod}
        self.patch_parts(output, path, addons, add_spec)

    def patch_mods(self, *mods):
        for m in mods: self.patch_mod(m)

if __name__ == '__main__':
    patcher = Patcher('GameData/ConfigurableContainers/TankTypes.cfg',
                      '/home/storage/Games/KSP_linux/PluginsArchives/Development/AT_KSP_Plugins/KSP-test/'
                      'KSP_test_1.3/GameData')

    patcher.part_filter = SearchQuery('PART/MODULE:.*Engines.*/')
    patcher.part_filter.Or('PART/MODULE:.*Converter.*/')
    patcher.part_filter.Or('PART/MODULE:.*Harvester.*/')
    patcher.part_filter.Or('PART/MODULE:.*Drill.*/')
    patcher.part_filter.Or('PART/MODULE:.*[Ff]uelSwitch/')

    xenon_titles = [
        (SearchTerm('name:xenonTank$'), Part.PatchValue('@', 'title', 'PB-X150 Pressurized Gass Container')),
        (SearchTerm('name:xenonTankLarge$'), Part.PatchValue('@', 'title', 'PB-X750 Pressurized Gass Container')),
        (SearchTerm('name:xenonTankRadial$'), Part.PatchValue('@', 'title', 'PB-X50R Pressurized Gass Container')),
    ]

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Squad_Patch.cfg'),
                        [('Squad', 'Parts')], xenon_titles)

    patcher.patch_mods('KWRocketry',
                       'Mk2Expansion',
                       'Mk3Expansion',
                       'SpaceY-Lifters',
                       'SpaceY-Expanded',
                       'FuelTanksPlus',
                       'ModRocketSys',
                       'NearFuturePropulsion',
                       'SPS', # Standard Propulsion Systems
                       'RaginCaucasian', # Mk2.5 spaceplane parts
                       'MunarIndustries', # Fuel Tank Expansion
                       )

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Tal-Tanks_Patch.cfg'),
                        [('ModsByTal', 'Parts'),
                        ],
                        [(SearchTerm(''), Module.Patch('!', 'ModuleFuelTanks'))],
                        add_spec=':AFTER[ModsByTal]')

    # USI uses FSfuelSwitch, so no patching for it
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-MKS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'Akita'),
    #                      ('UmbraSpaceIndustries', 'Konstruction'),
    #                      ('UmbraSpaceIndustries', 'Kontainers'),
    #                      ('UmbraSpaceIndustries', 'MKS'),
    #                      ('UmbraSpaceIndustries', 'ReactorPack'),
    #                     ],
    #                     add_spec=':NEEDS[MKS]:AFTER[MKS]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-LS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'LifeSupport'),
    #                      ],
    #                     add_spec=':NEEDS[USILifeSupport]:AFTER[USILifeSupport]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-FTT_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'FTT'),
    #                      ],
    #                     add_spec=':NEEDS[FTT]:AFTER[FTT]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-ExpPack_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'ExpPack'),
    #                      ],
    #                     add_spec=':NEEDS[ExpPack]:AFTER[ExpPack]')

    print('Done')

<commit_msg>Removed FOR[ConfigurableContainers] stanza, changed ref to KSP 1.4.1<commit_after>
import os, re
from KSPUtils import ConfigNode, NamedObject, Part, Module, Resource, SearchQuery, SearchTerm
from KSPUtils.Collections import ValueCollection

class TankType(NamedObject):
    def __init__(self):
        NamedObject.__init__(self)
        self.UnitsPerLiter = dict()

    def load(self, node):
        NamedObject.load(self, node)
        val = self.values.get('PossibleResources')
        if val:
            self.UnitsPerLiter = {r[0]: float(r[1]) for r in
                                  (res.strip().split() for res in val.value.split(';'))
                                  if len(r) > 1}

    @property
    def PossibleResources(self):
        return list(self.UnitsPerLiter.keys())

TankType.register('TANKTYPE')
TankType.mirror_value('UsefulVolumeRatio', float)
TankType.mirror_value('TankCostPerSurface', float)

class TanksLib(NamedObject): pass
TanksLib.setup_children_dict('types', 'TANKTYPE')

class ModuleTankManager(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleTankManager'

ModuleTankManager.mirror_value('Volume', float)
ModuleTankManager.mirror_value('DoCostPatch', bool)
ModuleTankManager.mirror_value('DoMassPatch', bool)
ModuleTankManager.mirror_value('IncludeTankTypes')
ModuleTankManager.mirror_value('ExcludeTankTypes')

class ModuleTank(Module):
    def __init__(self):
        NamedObject.__init__(self)
        self.name = 'ModuleSwitchableTank'

ModuleTank.mirror_value('Volume', float)
ModuleTank.mirror_value('InitialAmount', float)
ModuleTank.mirror_value('TankType')
ModuleTank.mirror_value('CurrentResource')
ModuleTank.mirror_value('ChooseTankType', bool)
ModuleTank.mirror_value('DoCostPatch', bool)
ModuleTank.mirror_value('DoMassPatch', bool)
ModuleTank.mirror_value('IncludeTankTypes')
ModuleTank.mirror_value('ExcludeTankTypes')

class Tank(NamedObject):
    type = 'TANK'

    def __init__(self, name=None):
        NamedObject.__init__(self)
        if name: self.name = name

Tank.mirror_value('Volume', float)
Tank.mirror_value('InitialAmount', float)
Tank.mirror_value('TankType')
Tank.mirror_value('CurrentResource')

class Patcher(object):
    def __init__(self, typelib, gamedata):
        self.game_data = gamedata
        self.tank_types_file = typelib
        self.tank_types = TanksLib.from_node(ConfigNode.Load(self.tank_types_file))
        self.types = self.tank_types.types
        self.part_filter = None

    def volume(self, tanktype, name, units):
        t = self.types[tanktype]
        upl = t.UnitsPerLiter.get(name)
        if not upl:
            print('WARNING: no UnitsPerLiter value for %s in %s:\n%s' % (name, tanktype, str(t)))
        return units / upl / t.UsefulVolumeRatio / 1e3 if upl else 0

    @staticmethod
    def get_parts(path):
        parts = []
        for dirpath, _dirnames, filenames in os.walk(path):
            for filename in filenames:
                if not filename.endswith('.cfg'): continue
                node = ConfigNode.Load(os.path.join(dirpath, filename))
                if node.name != 'PART': continue
                part = Part.from_node(node)
                resources = part.resources
                parts.append((part, resources))
        return parts

    @staticmethod
    def print_patches(stream, patches, header):
        stream.write('\n//%s\n//Automatically generated using PyKSPutils library\n' % header)
        stream.write('\n\n'.join(str(p) for p in patches))

    @staticmethod
    def add_patches(part, patch, addons):
        for term, addon in addons:
            if term.match(part):
                if isinstance(addon, ValueCollection.Value):
                    patch.AddValueItem(addon)
                elif isinstance(addon, NamedObject):
                    patch.AddChild(addon)

    def patch_1RES(self, stream, parts, tank_type, res_name, monotype=None, addons=None, add_spec=''):
        patches = []
        rate = self.volume(tank_type, res_name, 1)
        polytype = lambda part: True
        if monotype:
            if isinstance(monotype, (list, tuple)):
                polytype = lambda part: part.name not in monotype
            elif monotype in ('all', 'strict'):
                polytype = lambda part: False
            elif isinstance(monotype, str):
                mono_re = re.compile(monotype)
                polytype = lambda part: mono_re.match(part.name) is None
        for part, resources in parts:
            if len(resources) == 1 and res_name in resources:
                if self.part_filter is not None and self.part_filter.match(part): continue
                print('Patching %s' % part.name)
                res = resources[res_name]
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                V = res.maxAmount * rate
                ini = res.amount / res.maxAmount
                comment = '%f units of %s: conversion rate is %f m3/u' % (res.maxAmount, res_name, rate)
                patch.AddChild(Resource.Patch('!', res_name))
                can_change_type = polytype(part)
                if can_change_type or monotype != 'strict':
                    mgr = ModuleTankManager()
                    mgr.Volume = V
                    mgr.SetComment('Volume', comment)
                    mgr.DoCostPatch = True
                    mgr.DoMassPatch = True
                    if not can_change_type:
                        mgr.IncludeTankTypes = tank_type
                    tank = Tank()
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    tank.InitialAmount = ini
                    tank.Volume = 100
                    mgr.AddChild(tank)
                    patch.AddChild(mgr)
                else:
                    tank = ModuleTank()
                    tank.Volume = V
                    tank.SetComment('Volume', comment)
                    tank.InitialAmount = ini
                    tank.DoCostPatch = True
                    tank.DoMassPatch = True
                    tank.ChooseTankType = False
                    tank.TankType = tank_type
                    tank.CurrentResource = res_name
                    patch.AddChild(tank)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, '%s Tanks' % res_name)

    def patch_LFO(self, stream, parts, addons=None, add_spec=''):
        patches = []
        rate = self.volume('LiquidChemicals', 'LiquidFuel', 1) / 0.45
        for part, resources in parts:
            if len(resources) == 2 and 'LiquidFuel' in resources and 'Oxidizer' in resources:
                if self.part_filter is not None and self.part_filter.match(part): continue
                print('Patching %s' % part.name)
                lf = resources['LiquidFuel']
                patch = Part.Patch('@', part.name,
                                   ':HAS['
                                   '!MODULE[InterstellarFuelSwitch],'
                                   '!MODULE[FSfuelSwitch],'
                                   '!MODULE[ModuleB9PartSwitch]]'
                                   ':NEEDS[!modularFuelTanks&!RealFuels]'
                                   + add_spec)
                patch.AddChild(Resource.Patch('!', 'LiquidFuel'))
                patch.AddChild(Resource.Patch('!', 'Oxidizer'))
                mgr = ModuleTankManager()
                mgr.Volume = lf.maxAmount * rate
                mgr.SetComment('Volume', '%f units of LF: conversion rate is %f m3/u' % (lf.maxAmount, rate))
                mgr.DoCostPatch = True
                mgr.DoMassPatch = True
                tank = Tank('LFO')
                tank.Volume = 100
                mgr.AddChild(tank)
                patch.AddChild(mgr)
                if addons:
                    self.add_patches(part, patch, addons)
                patches.append(patch)
        if patches: self.print_patches(stream, patches, 'Rocket Fuel Tanks')

    def patch_parts(self, output, paths, addons=None, add_spec=''):
        for path in paths:
            parts = self.get_parts(os.path.join(self.game_data, *path))
            if not parts: continue
            with open(os.path.join(self.game_data, *output), 'w') as out:
                out.write('//Configurable Containers patch for %s\n' % os.path.join(*path))
                self.patch_LFO(out, parts, addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'LiquidChemicals', 'LiquidFuel', '.*[Ww]ing.*', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'LiquidChemicals', 'MonoPropellant', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Gases', 'XenonGas', 'strict', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Gases', 'ArgonGas', 'strict', addons=addons, add_spec=add_spec)
                self.patch_1RES(out, parts, 'Soil', 'Ore', addons=addons, add_spec=add_spec)
                out.write('\n//:mode=c#:\n')
            print('%s done.\n' % os.path.join(*path))

    def patch_mod(self, mod, addons=None, add_spec=''):
        output = ('ConfigurableContainers', 'Parts', '%s_Patch.cfg' % mod)
        path = [[mod]]
        add_spec += ':NEEDS[%(mod)s]:AFTER[%(mod)s]' % {'mod': mod}
        self.patch_parts(output, path, addons, add_spec)

    def patch_mods(self, *mods):
        for m in mods: self.patch_mod(m)

if __name__ == '__main__':
    patcher = Patcher('GameData/ConfigurableContainers/TankTypes.cfg',
                      '/home/storage/Games/KSP_linux/PluginsArchives/Development/AT_KSP_Plugins/KSP-test/'
                      'KSP_test_1.4.1/GameData')

    patcher.part_filter = SearchQuery('PART/MODULE:.*Engines.*/')
    patcher.part_filter.Or('PART/MODULE:.*Converter.*/')
    patcher.part_filter.Or('PART/MODULE:.*Harvester.*/')
    patcher.part_filter.Or('PART/MODULE:.*Drill.*/')
    patcher.part_filter.Or('PART/MODULE:.*[Ff]uelSwitch/')

    xenon_titles = [
        (SearchTerm('name:xenonTank$'), Part.PatchValue('@', 'title', 'PB-X150 Pressurized Gass Container')),
        (SearchTerm('name:xenonTankLarge$'), Part.PatchValue('@', 'title', 'PB-X750 Pressurized Gass Container')),
        (SearchTerm('name:xenonTankRadial$'), Part.PatchValue('@', 'title', 'PB-X50R Pressurized Gass Container')),
    ]

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Squad_Patch.cfg'),
                        [('Squad', 'Parts')], xenon_titles)

    patcher.patch_mods('KWRocketry',
                       'Mk2Expansion',
                       'Mk3Expansion',
                       'SpaceY-Lifters',
                       'SpaceY-Expanded',
                       'FuelTanksPlus',
                       'ModRocketSys',
                       'NearFuturePropulsion',
                       'SPS', # Standard Propulsion Systems
                       'RaginCaucasian', # Mk2.5 spaceplane parts
                       'MunarIndustries', # Fuel Tank Expansion
                       )

    patcher.patch_parts(('ConfigurableContainers', 'Parts', 'Tal-Tanks_Patch.cfg'),
                        [('ModsByTal', 'Parts'),
                        ],
                        [(SearchTerm(''), Module.Patch('!', 'ModuleFuelTanks'))],
                        add_spec=':AFTER[ModsByTal]')

    # USI uses FSfuelSwitch, so no patching for it
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-MKS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'Akita'),
    #                      ('UmbraSpaceIndustries', 'Konstruction'),
    #                      ('UmbraSpaceIndustries', 'Kontainers'),
    #                      ('UmbraSpaceIndustries', 'MKS'),
    #                      ('UmbraSpaceIndustries', 'ReactorPack'),
    #                     ],
    #                     add_spec=':NEEDS[MKS]:AFTER[MKS]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-LS_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'LifeSupport'),
    #                      ],
    #                     add_spec=':NEEDS[USILifeSupport]:AFTER[USILifeSupport]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-FTT_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'FTT'),
    #                      ],
    #                     add_spec=':NEEDS[FTT]:AFTER[FTT]')
    #
    # patcher.patch_parts(('ConfigurableContainers', 'Parts', 'USI-ExpPack_Patch.cfg'),
    #                     [('UmbraSpaceIndustries', 'ExpPack'),
    #                      ],
    #                     add_spec=':NEEDS[ExpPack]:AFTER[ExpPack]')

    print('Done')

<|endoftext|>