diff --git a/eos/graph/base.py b/eos/graph/base.py index daa9d498a..ed1fd3445 100644 --- a/eos/graph/base.py +++ b/eos/graph/base.py @@ -27,26 +27,7 @@ class Graph(metaclass=ABCMeta): def __init__(self): self._cache = {} - @abstractmethod - def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt): - raise NotImplementedError - _normalizers = {} - - def _normalizeParams(self, mainInput, miscInputs, fit, tgt): - if mainInput.unit in self._normalizers: - normalizer = self._normalizers[mainInput.unit] - newMainInput = [mainInput.handle, tuple(normalizer(v) for v in mainInput.value)] - else: - newMainInput = [mainInput.handle, mainInput.value] - newMiscInputs = [] - for miscInput in miscInputs: - if miscInput.unit in self._normalizers: - newMiscInput = [miscInput.handle, self._normalizers[miscInput.unit](miscInput.value)] - else: - newMiscInput = [miscInput.handle, miscInput.value] - newMiscInputs.append(newMiscInput) - return newMainInput, newMiscInputs ### Old stuff diff --git a/eos/graph/fitWarpTime.py b/eos/graph/fitWarpTime.py deleted file mode 100644 index c50f10588..000000000 --- a/eos/graph/fitWarpTime.py +++ /dev/null @@ -1,111 +0,0 @@ -import math - -from eos.const import FittingModuleState -from .base import Graph - - -AU_METERS = 149597870700 - - -class FitWarpTimeGraph(Graph): - - def __init__(self): - super().__init__() - self.subwarpSpeed = None - - def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt): - mainInput, miscInputs = self._normalizeParams(mainInput, miscInputs, fit, tgt) - - # limit all parameters to be within limits (time) - # pick getter according to x handle and y handle and run it - # un-render returned parameters if passed x is relative - return [], [] - - _normalizers = { - 'AU': lambda x: x * AU_METERS, - 'km': lambda x: x * 1000} - - - def getYForX(self, fit, extraData, distance): - if distance == 0: - return 0 - if fit.ID not in self._cache: - self.__generateCache(fit) - maxWarpSpeed = fit.warpSpeed - subwarpSpeed = self._cache[fit.ID]['cleanSubwarpSpeed'] - time = calculate_time_in_warp(maxWarpSpeed, subwarpSpeed, distance * AU_METERS) - return time - - def _getXLimits(self, fit, extraData): - return 0, fit.maxWarpDistance - - def __generateCache(self, fit): - modStates = {} - for mod in fit.modules: - if mod.item is not None and mod.item.group.name in ('Propulsion Module', 'Mass Entanglers', 'Cloaking Device') and mod.state >= FittingModuleState.ACTIVE: - modStates[mod] = mod.state - mod.state = FittingModuleState.ONLINE - projFitStates = {} - for projFit in fit.projectedFits: - projectionInfo = projFit.getProjectionInfo(fit.ID) - if projectionInfo is not None and projectionInfo.active: - projFitStates[projectionInfo] = projectionInfo.active - projectionInfo.active = False - projModStates = {} - for mod in fit.projectedModules: - if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE: - projModStates[mod] = mod.state - mod.state = FittingModuleState.ONLINE - projDroneStates = {} - for drone in fit.projectedDrones: - if drone.amountActive > 0: - projDroneStates[drone] = drone.amountActive - drone.amountActive = 0 - projFighterStates = {} - for fighter in fit.projectedFighters: - if fighter.active: - projFighterStates[fighter] = fighter.active - fighter.active = False - fit.calculateModifiedAttributes() - self._cache[fit.ID] = {'cleanSubwarpSpeed': fit.ship.getModifiedItemAttr('maxVelocity')} - for projInfo, state in projFitStates.items(): - projInfo.active = state - for mod, state in modStates.items(): - mod.state = state - for mod, state in projModStates.items(): - mod.state = state - for drone, amountActive in projDroneStates.items(): - drone.amountActive = amountActive - for fighter, state in projFighterStates.items(): - fighter.active = state - fit.calculateModifiedAttributes() - - -# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation -# with minor modifications -# Warp speed in AU/s, subwarp speed in m/s, distance in m -def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist): - - k_accel = max_warp_speed - k_decel = min(max_warp_speed / 3, 2) - - warp_dropout_speed = max_subwarp_speed / 2 - max_ms_warp_speed = max_warp_speed * AU_METERS - - accel_dist = AU_METERS - decel_dist = max_ms_warp_speed / k_decel - - minimum_dist = accel_dist + decel_dist - - cruise_time = 0 - - if minimum_dist > warp_dist: - max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel) - else: - cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed - - accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel - decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel - - total_time = cruise_time + accel_time + decel_time - return total_time diff --git a/gui/builtinGraphs/__init__.py b/gui/builtinGraphs/__init__.py index 6c7beeef6..ea8b49783 100644 --- a/gui/builtinGraphs/__init__.py +++ b/gui/builtinGraphs/__init__.py @@ -1,6 +1,6 @@ # noinspection PyUnresolvedReferences from gui.builtinGraphs import ( # noqa: E402,F401 - fitDamageStats, + # fitDamageStats, # fitDmgVsTime, # fitShieldRegenVsShieldPerc, # fitShieldAmountVsTime, diff --git a/gui/builtinGraphs/base.py b/gui/builtinGraphs/base.py index 11dce9d51..a1a88205b 100644 --- a/gui/builtinGraphs/base.py +++ b/gui/builtinGraphs/base.py @@ -22,18 +22,24 @@ from abc import ABCMeta, abstractmethod from collections import OrderedDict, namedtuple +YDef = namedtuple('YDef', ('handle', 'unit', 'label')) +XDef = namedtuple('XDef', ('handle', 'unit', 'label', 'mainInput')) +Input = namedtuple('Input', ('handle', 'unit', 'label', 'iconID', 'defaultValue', 'defaultRange', 'mainOnly')) +VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle', 'angleUnit', 'label')) + + class Graph(metaclass=ABCMeta): + # UI stuff views = [] - yTypes = None @classmethod def register(cls): Graph.views.append(cls) - def __init__(self, eosGraph): - self._eosGraph = eosGraph - self._cache = {} + def __init__(self): + self._plotCache = {} + self._calcCache = {} @property @abstractmethod @@ -84,25 +90,112 @@ class Graph(metaclass=ABCMeta): def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt=None): try: - plotData = self._cache[fit.ID][(ySpec, xSpec)] + plotData = self._plotCache[fit.ID][(ySpec, xSpec)] except KeyError: - plotData = self._eosGraph.getPlotPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt) - fitCache = self._cache.setdefault(fit.ID, {}) + plotData = self._calcPlotPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt) + fitCache = self._plotCache.setdefault(fit.ID, {}) fitCache[(ySpec, xSpec)] = plotData return plotData def clearCache(self, key=None): if key is None: - self._cache.clear() - elif key in self._cache: - del self._cache[key] - self._eosGraph.clearCache(key=key) + self._plotCache.clear() + self._calcCache.clear() + if key in self._plotCache: + del self._plotCache[key] + if key in self._calcCache: + del self._calcCache[key] + # Calculation stuff + def _calcPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt): + mainInput, miscInputs = self._normalizeParams(mainInput, miscInputs, fit, tgt) + mainInput, miscInputs = self._limitParams(mainInput, miscInputs, fit, tgt) + xs, ys = self._getPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt) + xs = self._denormalizeValues(xs, xSpec, fit, tgt) + ys = self._denormalizeValues(ys, ySpec, fit, tgt) + return xs, ys -YDef = namedtuple('YDef', ('handle', 'unit', 'label')) -XDef = namedtuple('XDef', ('handle', 'unit', 'label', 'mainInput')) -Input = namedtuple('Input', ('handle', 'unit', 'label', 'iconID', 'defaultValue', 'defaultRange', 'mainOnly')) -VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle', 'angleUnit', 'label')) + _normalizers = {} + + def _normalizeParams(self, mainInput, miscInputs, fit, tgt): + key = (mainInput.handle, mainInput.unit) + if key in self._normalizers: + normalizer = self._normalizers[key] + newMainInput = (mainInput.handle, tuple(normalizer(v, fit, tgt) for v in mainInput.value)) + else: + newMainInput = (mainInput.handle, mainInput.value) + newMiscInputs = [] + for miscInput in miscInputs: + key = (miscInput.handle, miscInput.unit) + if key in self._normalizers: + normalizer = self._normalizers[key] + newMiscInput = (miscInput.handle, normalizer(miscInput.value)) + else: + newMiscInput = (miscInput.handle, miscInput.value) + newMiscInputs.append(newMiscInput) + return newMainInput, newMiscInputs + + _limiters = {} + + def _limitParams(self, mainInput, miscInputs, fit, tgt): + + def limitToRange(val, limitRange): + if val is None: + return None + val = max(val, min(limitRange)) + val = min(val, max(limitRange)) + return val + + mainHandle, mainValue = mainInput + if mainHandle in self._limiters: + limiter = self._limiters[mainHandle] + newMainInput = (mainHandle, tuple(limitToRange(v, limiter(fit, tgt)) for v in mainValue)) + else: + newMainInput = mainInput + newMiscInputs = [] + for miscInput in miscInputs: + miscHandle, miscValue = miscInput + if miscHandle in self._limiters: + limiter = self._limiters[miscHandle] + newMiscInput = (miscHandle, limitToRange(miscValue, limiter(fit, tgt))) + newMiscInputs.append(newMiscInput) + else: + newMiscInputs.append(miscInput) + return newMainInput, newMiscInputs + + _getters = {} + + def _getPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt): + try: + getter = self._getters[(xSpec.handle, ySpec.handle)] + except KeyError: + return [], [] + else: + return getter(self, mainInput, miscInputs, fit, tgt) + + _denormalizers = {} + + def _denormalizeValues(self, values, axisSpec, fit, tgt): + key = (axisSpec.handle, axisSpec.unit) + if key in self._denormalizers: + denormalizer = self._denormalizers[key] + values = [denormalizer(v, fit, tgt) for v in values] + return values + + def _iterLinear(self, valRange, resolution=100): + rangeLow = min(valRange) + rangeHigh = max(valRange) + # Amount is amount of ranges between points here, not amount of points + step = (rangeHigh - rangeLow) / resolution + if step == 0: + yield rangeLow + else: + current = rangeLow + # Take extra half step to make sure end of range is always included + # despite any possible float errors + while current <= (rangeHigh + step / 2): + yield current + current += step # noinspection PyUnresolvedReferences diff --git a/gui/builtinGraphs/fitWarpTime.py b/gui/builtinGraphs/fitWarpTime.py index 468c246b3..d7337f00f 100644 --- a/gui/builtinGraphs/fitWarpTime.py +++ b/gui/builtinGraphs/fitWarpTime.py @@ -18,17 +18,23 @@ # ============================================================================= -from eos.graph.fitWarpTime import FitWarpTimeGraph as EosGraph +import math + +from eos.const import FittingModuleState from .base import Graph, XDef, YDef, Input +AU_METERS = 149597870700 + + class FitWarpTimeGraph(Graph): name = 'Warp Time' def __init__(self): - super().__init__(EosGraph()) + super().__init__() + # UI stuff @property def xDefs(self): return [ @@ -45,5 +51,110 @@ class FitWarpTimeGraph(Graph): Input(handle='distance', unit='AU', label='Distance', iconID=1391, defaultValue=50, defaultRange=(0, 50), mainOnly=False), Input(handle='distance', unit='km', label='Distance', iconID=1391, defaultValue=10000, defaultRange=(150, 5000), mainOnly=False)] + # Calculation stuff + _normalizers = { + ('distance', 'AU'): lambda v, fit, tgt: v * AU_METERS, + ('distance', 'km'): lambda v, fit, tgt: v * 1000} + + _limiters = { + 'distance': lambda fit, tgt: (0, fit.maxWarpDistance * AU_METERS)} + + _denormalizers = { + ('distance', 'AU'): lambda v, fit, tgt: v / AU_METERS, + ('distance', 'km'): lambda v, fit, tgt: v / 1000} + + def _distance2time(self, mainInput, miscInputs, fit, tgt): + xs = [] + ys = [] + subwarpSpeed = self.__getSubwarpSpeed(fit) + warpSpeed = fit.warpSpeed + for distance in self._iterLinear(mainInput[1]): + time = calculate_time_in_warp(subwarpSpeed, warpSpeed, distance) + xs.append(distance) + ys.append(time) + return xs, ys + + _getters = { + ('distance', 'time'): _distance2time} + + def __getSubwarpSpeed(self, fit): + try: + subwarpSpeed = self._calcCache[fit.ID]['cleanSubwarpSpeed'] + except KeyError: + modStates = {} + for mod in fit.modules: + if mod.item is not None and mod.item.group.name in ('Propulsion Module', 'Mass Entanglers', 'Cloaking Device') and mod.state >= FittingModuleState.ACTIVE: + modStates[mod] = mod.state + mod.state = FittingModuleState.ONLINE + projFitStates = {} + for projFit in fit.projectedFits: + projectionInfo = projFit.getProjectionInfo(fit.ID) + if projectionInfo is not None and projectionInfo.active: + projFitStates[projectionInfo] = projectionInfo.active + projectionInfo.active = False + projModStates = {} + for mod in fit.projectedModules: + if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE: + projModStates[mod] = mod.state + mod.state = FittingModuleState.ONLINE + projDroneStates = {} + for drone in fit.projectedDrones: + if drone.amountActive > 0: + projDroneStates[drone] = drone.amountActive + drone.amountActive = 0 + projFighterStates = {} + for fighter in fit.projectedFighters: + if fighter.active: + projFighterStates[fighter] = fighter.active + fighter.active = False + fit.calculateModifiedAttributes() + subwarpSpeed = fit.ship.getModifiedItemAttr('maxVelocity') + self._calcCache[fit.ID] = {'cleanSubwarpSpeed': subwarpSpeed} + for projInfo, state in projFitStates.items(): + projInfo.active = state + for mod, state in modStates.items(): + mod.state = state + for mod, state in projModStates.items(): + mod.state = state + for drone, amountActive in projDroneStates.items(): + drone.amountActive = amountActive + for fighter, state in projFighterStates.items(): + fighter.active = state + fit.calculateModifiedAttributes() + return subwarpSpeed + + +# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation +# with minor modifications +# Warp speed in AU/s, subwarp speed in m/s, distance in m +def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist): + + if warp_dist == 0: + return 0 + + k_accel = max_warp_speed + k_decel = min(max_warp_speed / 3, 2) + + warp_dropout_speed = max_subwarp_speed / 2 + max_ms_warp_speed = max_warp_speed * AU_METERS + + accel_dist = AU_METERS + decel_dist = max_ms_warp_speed / k_decel + + minimum_dist = accel_dist + decel_dist + + cruise_time = 0 + + if minimum_dist > warp_dist: + max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel) + else: + cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed + + accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel + decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel + + total_time = cruise_time + accel_time + decel_time + return total_time + FitWarpTimeGraph.register()