Merge remote-tracking branch 'origin/projections'
This commit is contained in:
32
config.py
32
config.py
@@ -17,7 +17,7 @@ debug = False
|
||||
# Defines if our saveddata will be in pyfa root or not
|
||||
saveInRoot = False
|
||||
|
||||
logLevel = logging.WARN
|
||||
logLevel = logging.DEBUG
|
||||
|
||||
# Version data
|
||||
version = "1.13.3"
|
||||
@@ -32,6 +32,21 @@ staticPath = None
|
||||
saveDB = None
|
||||
gameDB = None
|
||||
|
||||
|
||||
class StreamToLogger(object):
|
||||
"""
|
||||
Fake file-like stream object that redirects writes to a logger instance.
|
||||
From: http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/
|
||||
"""
|
||||
def __init__(self, logger, log_level=logging.INFO):
|
||||
self.logger = logger
|
||||
self.log_level = log_level
|
||||
self.linebuf = ''
|
||||
|
||||
def write(self, buf):
|
||||
for line in buf.rstrip().splitlines():
|
||||
self.logger.log(self.log_level, line.rstrip())
|
||||
|
||||
def __createDirs(path):
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
@@ -72,15 +87,14 @@ def defPaths():
|
||||
|
||||
logging.info("Starting pyfa")
|
||||
|
||||
# Redirect stderr to file if we're requested to do so
|
||||
stderrToFile = getattr(configforced, "stderrToFile", None)
|
||||
if stderrToFile is True:
|
||||
sys.stderr = open(os.path.join(savePath, "error_log.txt"), "w")
|
||||
if hasattr(sys, 'frozen'):
|
||||
stdout_logger = logging.getLogger('STDOUT')
|
||||
sl = StreamToLogger(stdout_logger, logging.INFO)
|
||||
sys.stdout = sl
|
||||
|
||||
# Same for stdout
|
||||
stdoutToFile = getattr(configforced, "stdoutToFile", None)
|
||||
if stdoutToFile is True:
|
||||
sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w")
|
||||
stderr_logger = logging.getLogger('STDERR')
|
||||
sl = StreamToLogger(stderr_logger, logging.ERROR)
|
||||
sys.stderr = sl
|
||||
|
||||
# Static EVE Data from the staticdata repository, should be in the staticdata
|
||||
# directory in our pyfa directory
|
||||
|
||||
16
eos/db/migrations/upgrade10.py
Normal file
16
eos/db/migrations/upgrade10.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Migration 10
|
||||
|
||||
- Adds active attribute to projected fits
|
||||
"""
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Update projectedFits schema to include active attribute
|
||||
try:
|
||||
saveddata_engine.execute("SELECT active FROM projectedFits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN active BOOLEAN")
|
||||
saveddata_engine.execute("UPDATE projectedFits SET active = 1")
|
||||
saveddata_engine.execute("UPDATE projectedFits SET amount = 1")
|
||||
@@ -17,9 +17,11 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String, Boolean
|
||||
from sqlalchemy.orm import relation, mapper
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.sql import and_
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.db.saveddata.module import modules_table
|
||||
@@ -45,33 +47,119 @@ fits_table = Table("fits", saveddata_meta,
|
||||
projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
|
||||
Column("victimID", ForeignKey("fits.ID"), primary_key = True),
|
||||
Column("amount", Integer))
|
||||
Column("amount", Integer, nullable = False, default = 1),
|
||||
Column("active", Boolean, nullable = False, default = 1),
|
||||
)
|
||||
|
||||
class ProjectedFit(object):
|
||||
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
||||
self.sourceID = sourceID
|
||||
self.source_fit = source_fit
|
||||
self.active = active
|
||||
self.__amount = amount
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
if self.source_fit.isInvalid:
|
||||
# Very rare for this to happen, but be prepared for it
|
||||
eos.db.saveddata_session.delete(self.source_fit)
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(self.victim_fit)
|
||||
|
||||
# We have a series of setters and getters here just in case someone
|
||||
# downgrades and screws up the table with NULL values
|
||||
@property
|
||||
def amount(self):
|
||||
return self.__amount or 1
|
||||
|
||||
@amount.setter
|
||||
def amount(self, amount):
|
||||
self.__amount = amount
|
||||
|
||||
def __repr__(self):
|
||||
return "ProjectedFit(sourceID={}, victimID={}, amount={}, active={}) at {}".format(
|
||||
self.sourceID, self.victimID, self.amount, self.active, hex(id(self))
|
||||
)
|
||||
|
||||
Fit._Fit__projectedFits = association_proxy(
|
||||
"victimOf", # look at the victimOf association...
|
||||
"source_fit", # .. and return the source fits
|
||||
creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit)
|
||||
)
|
||||
|
||||
mapper(Fit, fits_table,
|
||||
properties = {"_Fit__modules" : relation(Module, collection_class = HandledModuleList,
|
||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
||||
order_by = modules_table.c.position, cascade='all, delete, delete-orphan'),
|
||||
"_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
||||
"owner" : relation(User, backref = "fits"),
|
||||
"itemID" : fits_table.c.shipID,
|
||||
"shipID" : fits_table.c.shipID,
|
||||
"_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True),
|
||||
"_Fit__drones" : relation(Drone, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
|
||||
"_Fit__cargo" : relation(Cargo, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)),
|
||||
"_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True,
|
||||
primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
|
||||
"_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', backref='fit', single_parent=True,
|
||||
primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID,
|
||||
secondaryjoin = fitImplants_table.c.implantID == Implant.ID,
|
||||
secondary = fitImplants_table),
|
||||
"_Fit__character" : relation(Character, backref = "fits"),
|
||||
"_Fit__damagePattern" : relation(DamagePattern),
|
||||
"_Fit__targetResists" : relation(TargetResists),
|
||||
"_Fit__projectedFits" : relation(Fit,
|
||||
primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID,
|
||||
secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID,
|
||||
secondary = projectedFits_table,
|
||||
collection_class = HandledProjectedFitList)
|
||||
})
|
||||
properties = {
|
||||
"_Fit__modules": relation(
|
||||
Module,
|
||||
collection_class=HandledModuleList,
|
||||
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False),
|
||||
order_by=modules_table.c.position,
|
||||
cascade='all, delete, delete-orphan'),
|
||||
"_Fit__projectedModules": relation(
|
||||
Module,
|
||||
collection_class=HandledProjectedModList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True,
|
||||
primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)),
|
||||
"owner": relation(
|
||||
User,
|
||||
backref="fits"),
|
||||
"itemID": fits_table.c.shipID,
|
||||
"shipID": fits_table.c.shipID,
|
||||
"_Fit__boosters": relation(
|
||||
Booster,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True),
|
||||
"_Fit__drones": relation(
|
||||
Drone,
|
||||
collection_class=HandledDroneCargoList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True,
|
||||
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)),
|
||||
"_Fit__cargo": relation(
|
||||
Cargo,
|
||||
collection_class=HandledDroneCargoList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True,
|
||||
primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)),
|
||||
"_Fit__projectedDrones": relation(
|
||||
Drone,
|
||||
collection_class=HandledProjectedDroneList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
single_parent=True,
|
||||
primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)),
|
||||
"_Fit__implants": relation(
|
||||
Implant,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
backref='fit',
|
||||
single_parent=True,
|
||||
primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID,
|
||||
secondaryjoin=fitImplants_table.c.implantID == Implant.ID,
|
||||
secondary=fitImplants_table),
|
||||
"_Fit__character": relation(
|
||||
Character,
|
||||
backref="fits"),
|
||||
"_Fit__damagePattern": relation(DamagePattern),
|
||||
"_Fit__targetResists": relation(TargetResists),
|
||||
"projectedOnto": relationship(
|
||||
ProjectedFit,
|
||||
primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID,
|
||||
backref='source_fit',
|
||||
collection_class=attribute_mapped_collection('victimID'),
|
||||
cascade='all, delete, delete-orphan'),
|
||||
"victimOf": relationship(
|
||||
ProjectedFit,
|
||||
primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID,
|
||||
backref='victim_fit',
|
||||
collection_class=attribute_mapped_collection('sourceID'),
|
||||
cascade='all, delete, delete-orphan'),
|
||||
}
|
||||
)
|
||||
|
||||
mapper(ProjectedFit, projectedFits_table,
|
||||
properties = {
|
||||
"_ProjectedFit__amount": projectedFits_table.c.amount,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -232,11 +232,6 @@ class HandledProjectedDroneList(HandledDroneCargoList):
|
||||
if proj.isInvalid or not proj.item.isType("projected"):
|
||||
self.remove(proj)
|
||||
|
||||
class HandledProjectedFitList(HandledList):
|
||||
def append(self, proj):
|
||||
proj.projected = True
|
||||
list.append(self, proj)
|
||||
|
||||
class HandledItem(object):
|
||||
def preAssignItemAttr(self, *args, **kwargs):
|
||||
self.itemModifiedAttributes.preAssign(*args, **kwargs)
|
||||
|
||||
@@ -217,13 +217,16 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if attributeName not in self.__affectedBy:
|
||||
self.__affectedBy[attributeName] = {}
|
||||
affs = self.__affectedBy[attributeName]
|
||||
origin = self.fit.getOrigin()
|
||||
fit = origin if origin and origin != self.fit else self.fit
|
||||
# If there's no set for current fit in dictionary, create it
|
||||
if self.fit not in affs:
|
||||
affs[self.fit] = []
|
||||
if fit not in affs:
|
||||
affs[fit] = []
|
||||
# Reassign alias to list
|
||||
affs = affs[self.fit]
|
||||
affs = affs[fit]
|
||||
# Get modifier which helps to compose 'Affected by' map
|
||||
modifier = self.fit.getModifier()
|
||||
|
||||
# Add current affliction to list
|
||||
affs.append((modifier, operation, bonus, used))
|
||||
|
||||
|
||||
@@ -286,5 +286,10 @@ class Skill(HandledItem):
|
||||
copy = Skill(self.item, self.level, self.__ro)
|
||||
return copy
|
||||
|
||||
def __repr__(self):
|
||||
return "Skill(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
)
|
||||
|
||||
class ReadOnlyException(Exception):
|
||||
pass
|
||||
|
||||
@@ -29,6 +29,9 @@ from eos.saveddata.module import State
|
||||
from eos.saveddata.mode import Mode
|
||||
import eos.db
|
||||
import time
|
||||
import copy
|
||||
from utils.timer import Timer
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -40,14 +43,6 @@ except ImportError:
|
||||
|
||||
class Fit(object):
|
||||
"""Represents a fitting, with modules, ship, implants, etc."""
|
||||
EXTRA_ATTRIBUTES = {"armorRepair": 0,
|
||||
"hullRepair": 0,
|
||||
"shieldRepair": 0,
|
||||
"maxActiveDrones": 0,
|
||||
"maxTargetsLockedFromSkills": 2,
|
||||
"droneControlRange": 20000,
|
||||
"cloaked": False,
|
||||
"siege": False}
|
||||
|
||||
PEAK_RECHARGE = 0.25
|
||||
|
||||
@@ -61,7 +56,7 @@ class Fit(object):
|
||||
self.__cargo = HandledDroneCargoList()
|
||||
self.__implants = HandledImplantBoosterList()
|
||||
self.__boosters = HandledImplantBoosterList()
|
||||
self.__projectedFits = HandledProjectedFitList()
|
||||
#self.__projectedFits = {}
|
||||
self.__projectedModules = HandledProjectedModList()
|
||||
self.__projectedDrones = HandledProjectedDroneList()
|
||||
self.__character = None
|
||||
@@ -88,6 +83,10 @@ class Fit(object):
|
||||
|
||||
try:
|
||||
self.__ship = Ship(item)
|
||||
# @todo extra attributes is now useless, however it set to be
|
||||
# the same as ship attributes for ease (so we don't have to
|
||||
# change all instances in source). Remove this at some point
|
||||
self.extraAttributes = self.__ship.itemModifiedAttributes
|
||||
except ValueError:
|
||||
logger.error("Item (id: %d) is not a Ship", self.shipID)
|
||||
return
|
||||
@@ -124,8 +123,6 @@ class Fit(object):
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
self.ecmProjectedStr = 1
|
||||
self.extraAttributes = ModifiedAttributeDict(self)
|
||||
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
|
||||
|
||||
@property
|
||||
def targetResists(self):
|
||||
@@ -178,8 +175,11 @@ class Fit(object):
|
||||
def ship(self, ship):
|
||||
self.__ship = ship
|
||||
self.shipID = ship.item.ID if ship is not None else None
|
||||
# set mode of new ship
|
||||
self.mode = self.ship.validateModeItem(None) if ship is not None else None
|
||||
if ship is not None:
|
||||
# set mode of new ship
|
||||
self.mode = self.ship.validateModeItem(None) if ship is not None else None
|
||||
# set fit attributes the same as ship
|
||||
self.extraAttributes = self.ship.itemModifiedAttributes
|
||||
|
||||
@property
|
||||
def drones(self):
|
||||
@@ -207,7 +207,12 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
def projectedFits(self):
|
||||
return self.__projectedFits
|
||||
# only in extreme edge cases will the fit be invalid, but to be sure do
|
||||
# not return them.
|
||||
return [fit for fit in self.__projectedFits.values() if not fit.isInvalid]
|
||||
|
||||
def getProjectionInfo(self, fitID):
|
||||
return self.projectedOnto.get(fitID, None)
|
||||
|
||||
@property
|
||||
def projectedDrones(self):
|
||||
@@ -326,7 +331,7 @@ class Fit(object):
|
||||
if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
|
||||
else: return val
|
||||
|
||||
def clear(self):
|
||||
def clear(self, projected=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDPS = None
|
||||
self.__minerYield = None
|
||||
@@ -346,15 +351,36 @@ class Fit(object):
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
if self.ship is not None: self.ship.clear()
|
||||
c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
|
||||
if self.ship:
|
||||
self.ship.clear()
|
||||
|
||||
c = chain(
|
||||
self.modules,
|
||||
self.drones,
|
||||
self.boosters,
|
||||
self.implants,
|
||||
self.projectedDrones,
|
||||
self.projectedModules,
|
||||
(self.character, self.extraAttributes),
|
||||
)
|
||||
|
||||
for stuff in c:
|
||||
if stuff is not None and stuff != self: stuff.clear()
|
||||
if stuff is not None and stuff != self:
|
||||
stuff.clear()
|
||||
|
||||
# If this is the active fit that we are clearing, not a projected fit,
|
||||
# then this will run and clear the projected ships and flag the next
|
||||
# iteration to skip this part to prevent recursion.
|
||||
if not projected:
|
||||
for stuff in self.projectedFits:
|
||||
if stuff is not None and stuff != self:
|
||||
stuff.clear(projected=True)
|
||||
|
||||
#Methods to register and get the thing currently affecting the fit,
|
||||
#so we can correctly map "Affected By"
|
||||
def register(self, currModifier):
|
||||
def register(self, currModifier, origin=None):
|
||||
self.__modifier = currModifier
|
||||
self.__origin = origin
|
||||
if hasattr(currModifier, "itemModifiedAttributes"):
|
||||
currModifier.itemModifiedAttributes.fit = self
|
||||
if hasattr(currModifier, "chargeModifiedAttributes"):
|
||||
@@ -363,98 +389,129 @@ class Fit(object):
|
||||
def getModifier(self):
|
||||
return self.__modifier
|
||||
|
||||
def getOrigin(self):
|
||||
return self.__origin
|
||||
|
||||
def __calculateGangBoosts(self, runTime):
|
||||
logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, repr(self))
|
||||
for name, info in self.gangBoosts.iteritems():
|
||||
# Unpack all data required to run effect properly
|
||||
effect, thing = info[1]
|
||||
if effect.runTime == runTime:
|
||||
context = ("gang", thing.__class__.__name__.lower())
|
||||
if isinstance(thing, Module):
|
||||
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
||||
(effect.isType("active") and thing.state >= State.ACTIVE):
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
|
||||
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
|
||||
refreshBoosts = False
|
||||
if withBoosters is True:
|
||||
refreshBoosts = True
|
||||
if dirtyStorage is not None and self.ID in dirtyStorage:
|
||||
refreshBoosts = True
|
||||
if dirtyStorage is not None:
|
||||
dirtyStorage.update(self.boostsFits)
|
||||
if self.fleet is not None and refreshBoosts is True:
|
||||
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger)
|
||||
logger.debug("Starting fit calculation on: %s, withBoosters: %s", repr(self), withBoosters)
|
||||
|
||||
shadow = False
|
||||
if targetFit:
|
||||
logger.debug("Applying projections to target: %s", repr(targetFit))
|
||||
projectionInfo = self.getProjectionInfo(targetFit.ID)
|
||||
logger.debug("ProjectionInfo: %s", projectionInfo)
|
||||
if self == targetFit:
|
||||
copied = self # original fit
|
||||
shadow = True
|
||||
self = copy.deepcopy(self)
|
||||
self.fleet = copied.fleet
|
||||
logger.debug("Handling self projection - making shadow copy of fit. %s => %s", repr(copied), repr(self))
|
||||
# we delete the fit because when we copy a fit, flush() is
|
||||
# called to properly handle projection updates. However, we do
|
||||
# not want to save this fit to the database, so simply remove it
|
||||
eos.db.saveddata_session.delete(self)
|
||||
|
||||
if self.fleet is not None and withBoosters is True:
|
||||
logger.debug("Fleet is set, gathering gang boosts")
|
||||
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters)
|
||||
timer.checkpoint("Done calculating gang boosts for %s"%repr(self))
|
||||
elif self.fleet is None:
|
||||
self.gangBoosts = None
|
||||
if dirtyStorage is not None:
|
||||
try:
|
||||
dirtyStorage.remove(self.ID)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# If we're not explicitly asked to project fit onto something,
|
||||
# set self as target fit
|
||||
if targetFit is None:
|
||||
targetFit = self
|
||||
forceProjected = False
|
||||
# Else, we're checking all target projectee fits
|
||||
elif targetFit not in self.__calculatedTargets:
|
||||
self.__calculatedTargets.append(targetFit)
|
||||
targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
|
||||
forceProjected = True
|
||||
# Or do nothing if target fit is calculated
|
||||
projected = False
|
||||
else:
|
||||
return
|
||||
projected = True
|
||||
|
||||
# If fit is calculated and we have nothing to do here, get out
|
||||
if self.__calculated == True and forceProjected == False:
|
||||
|
||||
# A note on why projected fits don't get to return here. If we return
|
||||
# here, the projection afflictions will not be run as they are
|
||||
# intertwined into the regular fit calculations. So, even if the fit has
|
||||
# been calculated, we need to recalculate it again just to apply the
|
||||
# projections. This is in contract to gang boosts, which are only
|
||||
# calculated once, and their items are then looped and accessed with
|
||||
# self.gangBoosts.iteritems()
|
||||
# We might be able to exit early in the fit calculations if we separate
|
||||
# projections from the normal fit calculations. But we must ensure that
|
||||
# projection have modifying stuff applied, such as gang boosts and other
|
||||
# local modules that may help
|
||||
if self.__calculated and not projected:
|
||||
logger.debug("Fit has already been calculated and is not projected, returning: %s", repr(self))
|
||||
return
|
||||
|
||||
# Mark fit as calculated
|
||||
self.__calculated = True
|
||||
|
||||
# There's a few things to keep in mind here
|
||||
# 1: Early effects first, then regular ones, then late ones, regardless of anything else
|
||||
# 2: Some effects aren't implemented
|
||||
# 3: Some effects are implemented poorly and will just explode on us
|
||||
# 4: Errors should be handled gracefully and preferably without crashing unless serious
|
||||
for runTime in ("early", "normal", "late"):
|
||||
# Build a little chain of stuff
|
||||
# Avoid adding projected drones and modules when fit is projected onto self
|
||||
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
|
||||
if forceProjected is True:
|
||||
# if fit is being projected onto another fit
|
||||
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules)
|
||||
else:
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
self.projectedDrones, self.projectedModules)
|
||||
c = chain(
|
||||
(self.character, self.ship),
|
||||
self.drones,
|
||||
self.boosters,
|
||||
self.appliedImplants,
|
||||
self.modules
|
||||
)
|
||||
|
||||
if not projected:
|
||||
# if not a projected fit, add a couple of more things
|
||||
c = chain(c, (self.mode,), self.projectedDrones, self.projectedModules)
|
||||
|
||||
# We calculate gang bonuses first so that projected fits get them
|
||||
if self.gangBoosts is not None:
|
||||
contextMap = {Skill: "skill",
|
||||
Ship: "ship",
|
||||
Module: "module",
|
||||
Implant: "implant"}
|
||||
for name, info in self.gangBoosts.iteritems():
|
||||
# Unpack all data required to run effect properly
|
||||
effect, thing = info[1]
|
||||
if effect.runTime == runTime:
|
||||
context = ("gang", contextMap[type(thing)])
|
||||
if isinstance(thing, Module):
|
||||
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
||||
(effect.isType("active") and thing.state >= State.ACTIVE):
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
self.__calculateGangBoosts(runTime)
|
||||
|
||||
for item in c:
|
||||
# Registering the item about to affect the fit allows us to track "Affected By" relations correctly
|
||||
# Registering the item about to affect the fit allows us to
|
||||
# track "Affected By" relations correctly
|
||||
if item is not None:
|
||||
self.register(item)
|
||||
item.calculateModifiedAttributes(self, runTime, False)
|
||||
if forceProjected is True:
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
if projected is True:
|
||||
for _ in xrange(projectionInfo.amount):
|
||||
targetFit.register(item, origin=self)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
|
||||
for fit in self.projectedFits:
|
||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
timer.checkpoint('Done with runtime: %s'%runTime)
|
||||
|
||||
# Only apply projected fits if fit it not projected itself.
|
||||
if not projected:
|
||||
for fit in self.projectedFits:
|
||||
if fit.getProjectionInfo(self.ID).active:
|
||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
|
||||
timer.checkpoint('Done with fit calculation')
|
||||
|
||||
if shadow:
|
||||
logger.debug("Delete shadow fit object")
|
||||
del self
|
||||
|
||||
def fill(self):
|
||||
"""
|
||||
@@ -927,6 +984,19 @@ class Fit(object):
|
||||
c.append(deepcopy(i, memo))
|
||||
|
||||
for fit in self.projectedFits:
|
||||
copy.projectedFits.append(fit)
|
||||
copy.__projectedFits[fit.ID] = fit
|
||||
# this bit is required -- see GH issue # 83
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(fit)
|
||||
|
||||
return copy
|
||||
|
||||
def __repr__(self):
|
||||
return "Fit(ID={}, ship={}, name={}) at {}".format(
|
||||
self.ID, self.ship.item.name, self.name, hex(id(self))
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "{} ({})".format(
|
||||
self.name, self.ship.item.name
|
||||
)
|
||||
|
||||
@@ -639,6 +639,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copy.state = self.state
|
||||
return copy
|
||||
|
||||
def __repr__(self):
|
||||
if self.item:
|
||||
return "Module(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
)
|
||||
else:
|
||||
return "EmptyModule() at {}".format(hex(id(self)))
|
||||
|
||||
class Rack(Module):
|
||||
'''
|
||||
This is simply the Module class named something else to differentiate
|
||||
|
||||
@@ -26,6 +26,17 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Ship(ItemAttrShortcut, HandledItem):
|
||||
EXTRA_ATTRIBUTES = {
|
||||
"armorRepair": 0,
|
||||
"hullRepair": 0,
|
||||
"shieldRepair": 0,
|
||||
"maxActiveDrones": 0,
|
||||
"maxTargetsLockedFromSkills": 2,
|
||||
"droneControlRange": 20000,
|
||||
"cloaked": False,
|
||||
"siege": False
|
||||
}
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
if item.category.name != "Ship":
|
||||
@@ -34,7 +45,8 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
self.__item = item
|
||||
self.__modeItems = self.__getModeItems()
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
self.__itemModifiedAttributes.original = dict(self.item.attributes)
|
||||
self.__itemModifiedAttributes.original.update(self.EXTRA_ATTRIBUTES)
|
||||
|
||||
self.commandBonus = 0
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@ __all__ = [
|
||||
#"changeAffectingSkills",
|
||||
"tacticalMode",
|
||||
"targetResists",
|
||||
"priceClear"
|
||||
"priceClear",
|
||||
"amount",
|
||||
]
|
||||
|
||||
76
gui/builtinContextMenus/amount.py
Normal file
76
gui/builtinContextMenus/amount.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.itemStats import ItemStatsDialog
|
||||
import eos.types
|
||||
import gui.mainFrame
|
||||
import service
|
||||
import gui.globalEvents as GE
|
||||
import wx
|
||||
|
||||
class ChangeAmount(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("cargoItem","projectedFit")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
print selection
|
||||
return "Change {0} Quantity".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
srcContext = fullContext[0]
|
||||
dlg = AmountChanger(self.mainFrame, selection[0], srcContext)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
|
||||
ChangeAmount.register()
|
||||
|
||||
class AmountChanger(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, thing, context):
|
||||
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
|
||||
self.thing = thing
|
||||
self.context = context
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
|
||||
bSizer1.Add(self.input, 1, wx.ALL, 5)
|
||||
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
|
||||
self.button = wx.Button(self, wx.ID_OK, u"Done")
|
||||
bSizer1.Add(self.button, 0, wx.ALL, 5)
|
||||
|
||||
self.SetSizer(bSizer1)
|
||||
self.Layout()
|
||||
self.Centre(wx.BOTH)
|
||||
self.button.Bind(wx.EVT_BUTTON, self.change)
|
||||
|
||||
def change(self, event):
|
||||
sFit = service.Fit.getInstance()
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
fitID = mainFrame.getActiveFit()
|
||||
|
||||
if isinstance(self.thing, eos.types.Cargo):
|
||||
sFit.addCargo(fitID, self.thing.item.ID, int(self.input.GetLineText(0)), replace=True)
|
||||
elif isinstance(self.thing, eos.types.Fit):
|
||||
sFit.changeAmount(fitID, self.thing, int(self.input.GetLineText(0)))
|
||||
|
||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
event.Skip()
|
||||
self.Destroy()
|
||||
|
||||
## checks to make sure it's valid number
|
||||
def onChar(self, event):
|
||||
key = event.GetKeyCode()
|
||||
|
||||
acceptable_characters = "1234567890"
|
||||
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
||||
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
||||
event.Skip()
|
||||
return
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -29,67 +29,3 @@ class Cargo(ContextMenu):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
Cargo.register()
|
||||
|
||||
class CargoAmount(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
return srcContext in ("cargoItem",) and selection[0].amount >= 0
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Change {0} Quantity".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
srcContext = fullContext[0]
|
||||
dlg = CargoChanger(self.mainFrame, selection[0], srcContext)
|
||||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
|
||||
CargoAmount.register()
|
||||
|
||||
class CargoChanger(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, cargo, context):
|
||||
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
|
||||
self.cargo = cargo
|
||||
self.context = context
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
|
||||
bSizer1.Add(self.input, 1, wx.ALL, 5)
|
||||
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
|
||||
self.button = wx.Button(self, wx.ID_OK, u"Done")
|
||||
bSizer1.Add(self.button, 0, wx.ALL, 5)
|
||||
|
||||
self.SetSizer(bSizer1)
|
||||
self.Layout()
|
||||
self.Centre(wx.BOTH)
|
||||
self.button.Bind(wx.EVT_BUTTON, self.change)
|
||||
|
||||
def change(self, event):
|
||||
sFit = service.Fit.getInstance()
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
fitID = mainFrame.getActiveFit()
|
||||
|
||||
sFit.addCargo(fitID, self.cargo.item.ID, int(self.input.GetLineText(0)), replace=True)
|
||||
|
||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
event.Skip()
|
||||
self.Destroy()
|
||||
## checks to make sure it's valid number
|
||||
def onChar(self, event):
|
||||
key = event.GetKeyCode()
|
||||
|
||||
acceptable_characters = "1234567890"
|
||||
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
||||
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
||||
event.Skip()
|
||||
return
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ class ItemStats(ContextMenu):
|
||||
"cargoItem", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"skillItem", "projectedModule",
|
||||
"projectedDrone", "projectedCharge")
|
||||
"projectedDrone", "projectedCharge",
|
||||
"itemStats")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "{0} Stats".format(itmContext if itmContext is not None else "Item")
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from gui import builtinViewColumns
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
import gui.mainFrame
|
||||
|
||||
import wx
|
||||
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
|
||||
import service
|
||||
@@ -29,9 +29,12 @@ class BaseName(ViewColumn):
|
||||
name = "Base Name"
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.columnText = "Name"
|
||||
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons")
|
||||
self.mask = wx.LIST_MASK_TEXT
|
||||
self.projectedView = isinstance(fittingView, gui.projectedView.ProjectedView)
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, Drone):
|
||||
@@ -39,7 +42,12 @@ class BaseName(ViewColumn):
|
||||
elif isinstance(stuff, Cargo):
|
||||
return "%dx %s" % (stuff.amount, stuff.item.name)
|
||||
elif isinstance(stuff, Fit):
|
||||
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
|
||||
if self.projectedView:
|
||||
# we need a little more information for the projected view
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name)
|
||||
else:
|
||||
return "%s (%s)" % (stuff.name, stuff.ship.item.name)
|
||||
elif isinstance(stuff, Rack):
|
||||
if service.Fit.getInstance().serviceFittingOptions["rackLabels"]:
|
||||
if stuff.slot == Slot.MODE:
|
||||
|
||||
@@ -19,27 +19,21 @@
|
||||
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui import bitmapLoader
|
||||
import gui.mainFrame
|
||||
|
||||
import wx
|
||||
from eos.types import Drone, Module, Rack
|
||||
from eos.types import Drone, Module, Rack, Fit
|
||||
from eos.types import State as State_
|
||||
|
||||
class State(ViewColumn):
|
||||
name = "State"
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.resizable = False
|
||||
self.size = 16
|
||||
self.maxsize = self.size
|
||||
self.mask = wx.LIST_MASK_IMAGE
|
||||
for name, state in (("checked", wx.CONTROL_CHECKED), ("unchecked", 0)):
|
||||
bitmap = wx.EmptyBitmap(16, 16)
|
||||
dc = wx.MemoryDC()
|
||||
dc.SelectObject(bitmap)
|
||||
dc.SetBackground(wx.TheBrushList.FindOrCreateBrush(fittingView.GetBackgroundColour(), wx.SOLID))
|
||||
dc.Clear()
|
||||
wx.RendererNative.Get().DrawCheckBox(fittingView, dc, wx.Rect(0, 0, 16, 16), state)
|
||||
dc.Destroy()
|
||||
setattr(self, "%sId" % name, fittingView.imageList.Add(bitmap))
|
||||
|
||||
def getText(self, mod):
|
||||
return ""
|
||||
@@ -49,8 +43,14 @@ class State(ViewColumn):
|
||||
return State_.getName(mod.state).title()
|
||||
|
||||
def getImageId(self, stuff):
|
||||
generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(1).lower(), "icons")
|
||||
generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(-1).lower(), "icons")
|
||||
|
||||
if isinstance(stuff, Drone):
|
||||
return self.checkedId if stuff.amountActive > 0 else self.uncheckedId
|
||||
if stuff.amountActive > 0:
|
||||
return generic_active
|
||||
else:
|
||||
return generic_inactive
|
||||
elif isinstance(stuff, Rack):
|
||||
return -1
|
||||
elif isinstance(stuff, Module):
|
||||
@@ -58,11 +58,21 @@ class State(ViewColumn):
|
||||
return -1
|
||||
else:
|
||||
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons")
|
||||
elif isinstance(stuff, Fit):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
projectionInfo = stuff.getProjectionInfo(fitID)
|
||||
|
||||
if projectionInfo is None:
|
||||
return -1
|
||||
if projectionInfo.active:
|
||||
return generic_active
|
||||
return generic_inactive
|
||||
else:
|
||||
active = getattr(stuff, "active", None)
|
||||
if active is None:
|
||||
return -1
|
||||
else:
|
||||
return self.checkedId if active else self.uncheckedId
|
||||
if active:
|
||||
return generic_active
|
||||
return generic_inactive
|
||||
|
||||
State.register()
|
||||
|
||||
375
gui/itemStats.py
375
gui/itemStats.py
@@ -24,10 +24,11 @@ import bitmapLoader
|
||||
import sys
|
||||
import wx.lib.mixins.listctrl as listmix
|
||||
import wx.html
|
||||
from eos.types import Ship, Module, Skill, Booster, Implant, Drone, Mode
|
||||
from eos.types import Fit, Ship, Module, Skill, Booster, Implant, Drone, Mode
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
import service
|
||||
import config
|
||||
from gui.contextMenu import ContextMenu
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
@@ -549,15 +550,20 @@ class ItemEffects (wx.Panel):
|
||||
|
||||
|
||||
class ItemAffectedBy (wx.Panel):
|
||||
ORDER = [Ship, Mode, Module, Drone, Implant, Booster, Skill]
|
||||
ORDER = [Fit, Ship, Mode, Module, Drone, Implant, Booster, Skill]
|
||||
def __init__(self, parent, stuff, item):
|
||||
wx.Panel.__init__ (self, parent)
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.stuff = stuff
|
||||
self.item = item
|
||||
|
||||
self.toggleView = 1
|
||||
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||
|
||||
self.showRealNames = False
|
||||
self.showAttrView = False
|
||||
self.expand = -1
|
||||
|
||||
self.treeItems = []
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.affectedBy = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
|
||||
@@ -571,7 +577,10 @@ class ItemAffectedBy (wx.Panel):
|
||||
self.toggleExpandBtn = wx.ToggleButton( self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
bSizer.Add( self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||
|
||||
self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.toggleNameBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
bSizer.Add( self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||
|
||||
self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
bSizer.Add( self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||
|
||||
if stuff is not None:
|
||||
@@ -579,13 +588,36 @@ class ItemAffectedBy (wx.Panel):
|
||||
bSizer.Add( self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL)
|
||||
self.refreshBtn.Bind( wx.EVT_BUTTON, self.RefreshTree )
|
||||
|
||||
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode)
|
||||
self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleNameMode)
|
||||
self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleExpand)
|
||||
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode)
|
||||
|
||||
mainSizer.Add( bSizer, 0, wx.ALIGN_RIGHT)
|
||||
self.SetSizer(mainSizer)
|
||||
self.PopulateTree()
|
||||
self.Layout()
|
||||
self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu)
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
event.Skip()
|
||||
wx.CallAfter(self.spawnMenu, event.Item)
|
||||
|
||||
def spawnMenu(self, item):
|
||||
self.affectedBy.SelectItem(item)
|
||||
|
||||
stuff = self.affectedBy.GetPyData(item)
|
||||
# String is set as data when we are dealing with attributes, not stuff containers
|
||||
if stuff is None or isinstance(stuff, basestring):
|
||||
return
|
||||
contexts = []
|
||||
|
||||
# Skills are different in that they don't have itemModifiedAttributes,
|
||||
# which is needed if we send the container to itemStats dialog. So
|
||||
# instead, we send the item.
|
||||
type = stuff.__class__.__name__
|
||||
contexts.append(("itemStats", type))
|
||||
menu = ContextMenu.getMenu(stuff if type != "Skill" else stuff.item, *contexts)
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def ExpandCollapseTree(self):
|
||||
|
||||
@@ -607,18 +639,11 @@ class ItemAffectedBy (wx.Panel):
|
||||
def ToggleViewTree(self):
|
||||
self.Freeze()
|
||||
|
||||
root = self.affectedBy.GetRootItem()
|
||||
child,cookie = self.affectedBy.GetFirstChild(root)
|
||||
while child.IsOk():
|
||||
item,childcookie = self.affectedBy.GetFirstChild(child)
|
||||
while item.IsOk():
|
||||
change = self.affectedBy.GetPyData(item)
|
||||
display = self.affectedBy.GetItemText(item)
|
||||
self.affectedBy.SetItemText(item,change)
|
||||
self.affectedBy.SetPyData(item,display)
|
||||
item,childcookie = self.affectedBy.GetNextChild(child,childcookie)
|
||||
|
||||
child,cookie = self.affectedBy.GetNextChild(root,cookie)
|
||||
for item in self.treeItems:
|
||||
change = self.affectedBy.GetPyData(item)
|
||||
display = self.affectedBy.GetItemText(item)
|
||||
self.affectedBy.SetItemText(item, change)
|
||||
self.affectedBy.SetPyData(item, display)
|
||||
|
||||
self.Thaw()
|
||||
|
||||
@@ -633,33 +658,214 @@ class ItemAffectedBy (wx.Panel):
|
||||
event.Skip()
|
||||
|
||||
def ToggleViewMode(self, event):
|
||||
self.toggleView *=-1
|
||||
self.showAttrView = not self.showAttrView
|
||||
self.affectedBy.DeleteAllItems()
|
||||
self.PopulateTree()
|
||||
event.Skip()
|
||||
|
||||
def ToggleNameMode(self, event):
|
||||
self.showRealNames = not self.showRealNames
|
||||
self.ToggleViewTree()
|
||||
event.Skip()
|
||||
|
||||
def PopulateTree(self):
|
||||
# sheri was here
|
||||
del self.treeItems[:]
|
||||
root = self.affectedBy.AddRoot("WINPWNZ0R")
|
||||
self.affectedBy.SetPyData(root, None)
|
||||
|
||||
self.imageList = wx.ImageList(16, 16)
|
||||
self.affectedBy.SetImageList(self.imageList)
|
||||
|
||||
cont = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
|
||||
things = {}
|
||||
if self.showAttrView:
|
||||
self.buildAttributeView(root)
|
||||
else:
|
||||
self.buildModuleView(root)
|
||||
|
||||
for attrName in cont.iterAfflictions():
|
||||
self.ExpandCollapseTree()
|
||||
|
||||
def sortAttrDisplayName(self, attr):
|
||||
info = self.stuff.item.attributes.get(attr)
|
||||
if info and info.displayName != "":
|
||||
return info.displayName
|
||||
|
||||
return attr
|
||||
|
||||
def buildAttributeView(self, root):
|
||||
# We first build a usable dictionary of items. The key is either a fit
|
||||
# if the afflictions stem from a projected fit, or self.stuff if they
|
||||
# are local afflictions (everything else, even gang boosts at this time)
|
||||
# The value of this is yet another dictionary in the following format:
|
||||
#
|
||||
# "attribute name": {
|
||||
# "Module Name": [
|
||||
# class of affliction,
|
||||
# affliction item (required due to GH issue #335)
|
||||
# modifier type
|
||||
# amount of modification
|
||||
# whether this affliction was projected
|
||||
# ]
|
||||
# }
|
||||
|
||||
attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
|
||||
container = {}
|
||||
for attrName in attributes.iterAfflictions():
|
||||
# if value is 0 or there has been no change from original to modified, return
|
||||
if cont[attrName] == (cont.getOriginal(attrName) or 0):
|
||||
if attributes[attrName] == (attributes.getOriginal(attrName) or 0):
|
||||
continue
|
||||
for fit, afflictors in cont.getAfflictions(attrName).iteritems():
|
||||
|
||||
for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
|
||||
for afflictor, modifier, amount, used in afflictors:
|
||||
|
||||
if not used or afflictor.item is None:
|
||||
continue
|
||||
|
||||
if afflictor.item.name not in things:
|
||||
things[afflictor.item.name] = [type(afflictor), set(), []]
|
||||
if fit.ID != self.activeFit:
|
||||
# affliction fit does not match our fit
|
||||
if fit not in container:
|
||||
container[fit] = {}
|
||||
items = container[fit]
|
||||
else:
|
||||
# local afflictions
|
||||
if self.stuff not in container:
|
||||
container[self.stuff] = {}
|
||||
items = container[self.stuff]
|
||||
|
||||
info = things[afflictor.item.name]
|
||||
# items hold our module: info mappings
|
||||
if attrName not in items:
|
||||
items[attrName] = []
|
||||
|
||||
if afflictor == self.stuff and getattr(afflictor, 'charge', None):
|
||||
# we are showing a charges modifications, see #335
|
||||
item = afflictor.charge
|
||||
else:
|
||||
item = afflictor.item
|
||||
|
||||
items[attrName].append((type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False)))
|
||||
|
||||
# Make sure projected fits are on top
|
||||
rootOrder = container.keys()
|
||||
rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
|
||||
|
||||
# Now, we take our created dictionary and start adding stuff to our tree
|
||||
for thing in rootOrder:
|
||||
# This block simply directs which parent we are adding to (root or projected fit)
|
||||
if thing == self.stuff:
|
||||
parent = root
|
||||
else: # projected fit
|
||||
icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
|
||||
child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
|
||||
parent = child
|
||||
|
||||
attributes = container[thing]
|
||||
attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName)
|
||||
|
||||
for attrName in attrOrder:
|
||||
attrInfo = self.stuff.item.attributes.get(attrName)
|
||||
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.icon is not None:
|
||||
iconFile = attrInfo.icon.iconFile
|
||||
icon = bitmapLoader.getBitmap(iconFile, "pack")
|
||||
if icon is None:
|
||||
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
|
||||
else:
|
||||
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
|
||||
|
||||
if self.showRealNames:
|
||||
display = attrName
|
||||
saved = displayName
|
||||
else:
|
||||
display = displayName
|
||||
saved = attrName
|
||||
|
||||
# this is the attribute node
|
||||
child = self.affectedBy.AppendItem(parent, display, attrIcon)
|
||||
self.affectedBy.SetPyData(child, saved)
|
||||
self.treeItems.append(child)
|
||||
|
||||
items = attributes[attrName]
|
||||
items.sort(key=lambda x: self.ORDER.index(x[0]))
|
||||
for itemInfo in items:
|
||||
afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo
|
||||
|
||||
if afflictorType == Ship:
|
||||
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
|
||||
elif item.icon:
|
||||
bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
|
||||
displayStr = item.name
|
||||
|
||||
if projected:
|
||||
displayStr += " (projected)"
|
||||
|
||||
if attrModifier == "s*":
|
||||
attrModifier = "*"
|
||||
penalized = "(penalized)"
|
||||
else:
|
||||
penalized = ""
|
||||
|
||||
# this is the Module node, the attribute will be attached to this
|
||||
display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized)
|
||||
treeItem = self.affectedBy.AppendItem(child, display, itemIcon)
|
||||
self.affectedBy.SetPyData(treeItem, afflictor)
|
||||
|
||||
|
||||
def buildModuleView(self, root):
|
||||
# We first build a usable dictionary of items. The key is either a fit
|
||||
# if the afflictions stem from a projected fit, or self.stuff if they
|
||||
# are local afflictions (everything else, even gang boosts at this time)
|
||||
# The value of this is yet another dictionary in the following format:
|
||||
#
|
||||
# "Module Name": [
|
||||
# class of affliction,
|
||||
# set of afflictors (such as 2 of the same module),
|
||||
# info on affliction (attribute name, modifier, and modification amount),
|
||||
# item that will be used to determine icon (required due to GH issue #335)
|
||||
# whether this affliction is actually used (unlearned skills are not used)
|
||||
# ]
|
||||
|
||||
attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes
|
||||
container = {}
|
||||
for attrName in attributes.iterAfflictions():
|
||||
# if value is 0 or there has been no change from original to modified, return
|
||||
if attributes[attrName] == (attributes.getOriginal(attrName) or 0):
|
||||
continue
|
||||
|
||||
for fit, afflictors in attributes.getAfflictions(attrName).iteritems():
|
||||
for afflictor, modifier, amount, used in afflictors:
|
||||
if not used or getattr(afflictor, 'item', None) is None:
|
||||
continue
|
||||
|
||||
if fit.ID != self.activeFit:
|
||||
# affliction fit does not match our fit
|
||||
if fit not in container:
|
||||
container[fit] = {}
|
||||
items = container[fit]
|
||||
else:
|
||||
# local afflictions
|
||||
if self.stuff not in container:
|
||||
container[self.stuff] = {}
|
||||
items = container[self.stuff]
|
||||
|
||||
if afflictor == self.stuff and getattr(afflictor, 'charge', None):
|
||||
# we are showing a charges modifications, see #335
|
||||
item = afflictor.charge
|
||||
else:
|
||||
item = afflictor.item
|
||||
|
||||
# items hold our module: info mappings
|
||||
if item.name not in items:
|
||||
items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)]
|
||||
|
||||
info = items[item.name]
|
||||
info[1].add(afflictor)
|
||||
# If info[1] > 1, there are two separate modules working.
|
||||
# Check to make sure we only include the modifier once
|
||||
@@ -668,63 +874,86 @@ class ItemAffectedBy (wx.Panel):
|
||||
continue
|
||||
info[2].append((attrName, modifier, amount))
|
||||
|
||||
order = things.keys()
|
||||
order.sort(key=lambda x: (self.ORDER.index(things[x][0]), x))
|
||||
# Make sure projected fits are on top
|
||||
rootOrder = container.keys()
|
||||
rootOrder.sort(key=lambda x: self.ORDER.index(type(x)))
|
||||
|
||||
for itemName in order:
|
||||
info = things[itemName]
|
||||
# Now, we take our created dictionary and start adding stuff to our tree
|
||||
for thing in rootOrder:
|
||||
# This block simply directs which parent we are adding to (root or projected fit)
|
||||
if thing == self.stuff:
|
||||
parent = root
|
||||
else: # projected fit
|
||||
icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
|
||||
child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon)
|
||||
parent = child
|
||||
|
||||
afflictorType, afflictors, attrData = info
|
||||
counter = len(afflictors)
|
||||
items = container[thing]
|
||||
order = items.keys()
|
||||
order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x))
|
||||
|
||||
baseAfflictor = afflictors.pop()
|
||||
if afflictorType == Ship:
|
||||
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
|
||||
elif baseAfflictor.item.icon:
|
||||
bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
for itemName in order:
|
||||
info = items[itemName]
|
||||
afflictorType, afflictors, attrData, item, projected = info
|
||||
counter = len(afflictors)
|
||||
if afflictorType == Ship:
|
||||
itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))
|
||||
elif item.icon:
|
||||
bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack")
|
||||
itemIcon = self.imageList.Add(bitmap) if bitmap else -1
|
||||
else:
|
||||
itemIcon = -1
|
||||
|
||||
child = self.affectedBy.AppendItem(root, "%s" % itemName if counter == 1 else "%s x %d" % (itemName,counter), itemIcon)
|
||||
displayStr = itemName
|
||||
|
||||
if counter > 0:
|
||||
attributes = []
|
||||
for attrName, attrModifier, attrAmount in attrData:
|
||||
attrInfo = self.stuff.item.attributes.get(attrName)
|
||||
displayName = attrInfo.displayName if attrInfo else ""
|
||||
if counter > 1:
|
||||
displayStr += " x {}".format(counter)
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.icon is not None:
|
||||
iconFile = attrInfo.icon.iconFile
|
||||
icon = bitmapLoader.getBitmap(iconFile, "pack")
|
||||
if icon is None:
|
||||
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
|
||||
if projected:
|
||||
displayStr += " (projected)"
|
||||
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
# this is the Module node, the attribute will be attached to this
|
||||
child = self.affectedBy.AppendItem(parent, displayStr, itemIcon)
|
||||
self.affectedBy.SetPyData(child, afflictors.pop())
|
||||
|
||||
if counter > 0:
|
||||
attributes = []
|
||||
for attrName, attrModifier, attrAmount in attrData:
|
||||
attrInfo = self.stuff.item.attributes.get(attrName)
|
||||
displayName = attrInfo.displayName if attrInfo else ""
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.icon is not None:
|
||||
iconFile = attrInfo.icon.iconFile
|
||||
icon = bitmapLoader.getBitmap(iconFile, "pack")
|
||||
if icon is None:
|
||||
icon = bitmapLoader.getBitmap("transparent16x16", "icons")
|
||||
|
||||
attrIcon = self.imageList.Add(icon)
|
||||
else:
|
||||
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
|
||||
else:
|
||||
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
|
||||
else:
|
||||
attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack"))
|
||||
|
||||
if attrModifier == "s*":
|
||||
attrModifier = "*"
|
||||
penalized = "(penalized)"
|
||||
else:
|
||||
penalized = ""
|
||||
if attrModifier == "s*":
|
||||
attrModifier = "*"
|
||||
penalized = "(penalized)"
|
||||
else:
|
||||
penalized = ""
|
||||
|
||||
attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon))
|
||||
attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon))
|
||||
|
||||
attrSorted = sorted(attributes, key = lambda attribName: attribName[0])
|
||||
attrSorted = sorted(attributes, key = lambda attribName: attribName[0])
|
||||
for attr in attrSorted:
|
||||
attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
|
||||
|
||||
for attr in attrSorted:
|
||||
attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr
|
||||
if self.toggleView == 1:
|
||||
treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized), attrIcon)
|
||||
self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized))
|
||||
else:
|
||||
treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized), attrIcon)
|
||||
self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized))
|
||||
|
||||
self.ExpandCollapseTree()
|
||||
if self.showRealNames:
|
||||
display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)
|
||||
saved = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)
|
||||
else:
|
||||
display = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)
|
||||
saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)
|
||||
|
||||
treeitem = self.affectedBy.AppendItem(child, display, attrIcon)
|
||||
self.affectedBy.SetPyData(treeitem, saved)
|
||||
self.treeItems.append(treeitem)
|
||||
|
||||
@@ -36,7 +36,7 @@ from service.fleet import Fleet
|
||||
from service.settings import SettingsProvider
|
||||
from service.port import Port
|
||||
|
||||
logger = logging.getLogger("pyfa.service.fit")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FitBackupThread(threading.Thread):
|
||||
def __init__(self, path, callback):
|
||||
@@ -175,10 +175,14 @@ class Fit(object):
|
||||
fit = eos.db.getFit(fitID)
|
||||
sFleet = Fleet.getInstance()
|
||||
sFleet.removeAssociatedFleetData(fit)
|
||||
self.removeProjectedData(fitID)
|
||||
|
||||
eos.db.remove(fit)
|
||||
|
||||
# refresh any fits this fit is projected onto. Otherwise, if we have
|
||||
# already loaded those fits, they will not reflect the changes
|
||||
for projection in fit.projectedOnto.values():
|
||||
eos.db.saveddata_session.refresh(projection.victim_fit)
|
||||
|
||||
def copyFit(self, fitID):
|
||||
fit = eos.db.getFit(fitID)
|
||||
newFit = copy.deepcopy(fit)
|
||||
@@ -193,14 +197,6 @@ class Fit(object):
|
||||
fit.clear()
|
||||
return fit
|
||||
|
||||
def removeProjectedData(self, fitID):
|
||||
"""Removes projection relation from ships that have fitID as projection. See GitHub issue #90"""
|
||||
fit = eos.db.getFit(fitID)
|
||||
fits = eos.db.getProjectedFits(fitID)
|
||||
|
||||
for projectee in fits:
|
||||
projectee.projectedFits.remove(fit)
|
||||
|
||||
def toggleFactorReload(self, fitID):
|
||||
if fitID is None:
|
||||
return None
|
||||
@@ -236,6 +232,7 @@ class Fit(object):
|
||||
return None
|
||||
fit = eos.db.getFit(fitID)
|
||||
inited = getattr(fit, "inited", None)
|
||||
|
||||
if inited is None or inited is False:
|
||||
sFleet = Fleet.getInstance()
|
||||
f = sFleet.getLinearFleet(fit)
|
||||
@@ -246,6 +243,7 @@ class Fit(object):
|
||||
fit.fleet = f
|
||||
|
||||
if not projected:
|
||||
print "Not projected, getting projected fits"
|
||||
for fitP in fit.projectedFits:
|
||||
self.getFit(fitP.ID, projected = True)
|
||||
self.recalc(fit, withBoosters=True)
|
||||
@@ -322,9 +320,14 @@ class Fit(object):
|
||||
eager=("attributes", "group.category"))
|
||||
|
||||
if isinstance(thing, eos.types.Fit):
|
||||
if thing.ID == fitID:
|
||||
if thing in fit.projectedFits:
|
||||
return
|
||||
fit.projectedFits.append(thing)
|
||||
|
||||
fit.__projectedFits[thing.ID] = thing
|
||||
|
||||
# this bit is required -- see GH issue # 83
|
||||
eos.db.saveddata_session.flush()
|
||||
eos.db.saveddata_session.refresh(thing)
|
||||
elif thing.category.name == "Drone":
|
||||
drone = None
|
||||
for d in fit.projectedDrones.find(thing):
|
||||
@@ -363,6 +366,22 @@ class Fit(object):
|
||||
thing.state = self.__getProposedState(thing, click)
|
||||
if not thing.canHaveState(thing.state, fit):
|
||||
thing.state = State.OFFLINE
|
||||
elif isinstance(thing, eos.types.Fit):
|
||||
print "toggle fit"
|
||||
projectionInfo = thing.getProjectionInfo(fitID)
|
||||
if projectionInfo:
|
||||
projectionInfo.active = not projectionInfo.active
|
||||
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
|
||||
def changeAmount(self, fitID, projected_fit, amount):
|
||||
"""Change amount of projected fits"""
|
||||
fit = eos.db.getFit(fitID)
|
||||
amount = min(20, max(1, amount)) # 1 <= a <= 20
|
||||
projectionInfo = projected_fit.getProjectionInfo(fitID)
|
||||
if projectionInfo:
|
||||
projectionInfo.amount = amount
|
||||
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
@@ -374,7 +393,8 @@ class Fit(object):
|
||||
elif isinstance(thing, eos.types.Module):
|
||||
fit.projectedModules.remove(thing)
|
||||
else:
|
||||
fit.projectedFits.remove(thing)
|
||||
del fit.__projectedFits[thing.ID]
|
||||
#fit.projectedFits.remove(thing)
|
||||
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
@@ -921,8 +941,9 @@ class Fit(object):
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
|
||||
def recalc(self, fit, withBoosters=False):
|
||||
def recalc(self, fit, withBoosters=True):
|
||||
logger.debug("="*10+"recalc"+"="*10)
|
||||
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
|
||||
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
|
||||
fit.clear()
|
||||
fit.calculateModifiedAttributes(withBoosters=withBoosters, dirtyStorage=self.dirtyFitIDs)
|
||||
fit.clear()
|
||||
fit.calculateModifiedAttributes(withBoosters=withBoosters)
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
import time
|
||||
|
||||
class Timer(object):
|
||||
"""
|
||||
Generic timing class for simple profiling.
|
||||
class Timer():
|
||||
def __init__(self, name='', logger=None):
|
||||
self.name = name
|
||||
self.start = time.time()
|
||||
self.__last = self.start
|
||||
self.logger = logger
|
||||
|
||||
Usage:
|
||||
@property
|
||||
def elapsed(self):
|
||||
return (time.time() - self.start)*1000
|
||||
|
||||
with Timer(verbose=True) as t:
|
||||
# code to be timed
|
||||
time.sleep(5)
|
||||
@property
|
||||
def last(self):
|
||||
return (time.time() - self.__last)*1000
|
||||
|
||||
Output:
|
||||
elapsed time: 5000.000 ms
|
||||
|
||||
Can also access time with t.secs
|
||||
"""
|
||||
def __init__(self, verbose=False):
|
||||
self.verbose = verbose
|
||||
def checkpoint(self, name=''):
|
||||
text = 'Timer - {timer} - {checkpoint} - {last:.2f}ms ({elapsed:.2f}ms elapsed)'.format(
|
||||
timer=self.name,
|
||||
checkpoint=name,
|
||||
last=self.last,
|
||||
elapsed=self.elapsed
|
||||
).strip()
|
||||
self.__last = time.time()
|
||||
if self.logger:
|
||||
self.logger.debug(text)
|
||||
else:
|
||||
print text
|
||||
|
||||
def __enter__(self):
|
||||
self.start = time.time()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.end = time.time()
|
||||
self.secs = self.end - self.start
|
||||
self.msecs = self.secs * 1000 # millisecs
|
||||
if self.verbose:
|
||||
print 'elapsed time: %f ms' % self.msecs
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.checkpoint('finished')
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user