diff --git a/eos/db/__init__.py b/eos/db/__init__.py
index 789141435..a02979112 100644
--- a/eos/db/__init__.py
+++ b/eos/db/__init__.py
@@ -66,7 +66,7 @@ from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithS
getCharacterList, getPrice, getDamagePatternList, getDamagePattern, \
getFitList, getFleetList, getFleet, save, remove, commit, add, \
getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \
- getSquad, getBoosterFits, getProjectedFits
+ getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists
#If using in memory saveddata, you'll want to reflect it so the data structure is good.
if config.saveddata_connectionstring == "sqlite:///:memory:":
diff --git a/eos/db/migration.py b/eos/db/migration.py
index 2c67b8334..81399f4a5 100644
--- a/eos/db/migration.py
+++ b/eos/db/migration.py
@@ -4,6 +4,7 @@ def update(saveddata_engine):
checkPriceFailures(saveddata_engine)
checkApiDefaultChar(saveddata_engine)
checkFitBooster(saveddata_engine)
+ checktargetResists(saveddata_engine)
def checkPriceFailures(saveddata_engine):
# Check if we have 'failed' column
@@ -57,3 +58,19 @@ def checkFitBooster(saveddata_engine):
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN booster BOOLEAN;")
# Set NULL data to 0 (needed in case of downgrade, see GH issue #62
saveddata_engine.execute("UPDATE fits SET booster = 0 WHERE booster IS NULL;")
+
+def checktargetResists(saveddata_engine):
+ try:
+ saveddata_engine.execute("SELECT * FROM fits LIMIT 1")
+ # If table doesn't exist, it means we're doing everything from scratch
+ # and sqlalchemy will process everything as needed
+ except sqlalchemy.exc.DatabaseError:
+ pass
+ # If not, we're running on top of existing DB
+ else:
+ # Check that we have columns
+ try:
+ saveddata_engine.execute("SELECT targetResistsID FROM fits LIMIT 1")
+ # If we don't, create them
+ except sqlalchemy.exc.DatabaseError:
+ saveddata_engine.execute("ALTER TABLE fits ADD COLUMN targetResistsID INTEGER;")
diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index 70c7b22ec..31e71c01a 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -1,3 +1,3 @@
__all__ = ["character", "fit", "module", "user", "skill", "price",
"booster", "drone", "implant", "fleet", "damagePattern",
- "miscData"]
+ "miscData", "targetResists"]
diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py
index ccb8bbb71..b06b8f924 100644
--- a/eos/db/saveddata/fit.py
+++ b/eos/db/saveddata/fit.py
@@ -26,10 +26,11 @@ from eos.db.saveddata.module import modules_table
from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_table
-from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern
+from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
HandledProjectedFitList, HandledCargoList
+
fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key = True),
Column("ownerID", ForeignKey("users.ID"), nullable = True, index = True),
@@ -38,7 +39,8 @@ fits_table = Table("fits", saveddata_meta,
Column("timestamp", Integer, nullable = False),
Column("characterID", ForeignKey("characters.ID"), nullable = True),
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
- Column("booster", Boolean, nullable = False, index = True, default = 0))
+ Column("booster", Boolean, nullable = False, index = True, default = 0),
+ Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True))
projectedFits_table = Table("projectedFits", saveddata_meta,
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
@@ -64,6 +66,7 @@ mapper(Fit, fits_table,
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,
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index ddb8d53ac..e939bb65f 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -19,7 +19,7 @@
from eos.db.util import processEager, processWhere
from eos.db import saveddata_session, sd_lock
-from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad
+from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists
from eos.db.saveddata.fleet import squadmembers_table
from eos.db.saveddata.fit import projectedFits_table
from sqlalchemy.sql import and_
@@ -322,6 +322,12 @@ def getDamagePatternList(eager=None):
patterns = saveddata_session.query(DamagePattern).options(*eager).all()
return patterns
+def getTargetResistsList(eager=None):
+ eager = processEager(eager)
+ with sd_lock:
+ patterns = saveddata_session.query(TargetResists).options(*eager).all()
+ return patterns
+
@cachedQuery(DamagePattern, 1, "lookfor")
def getDamagePattern(lookfor, eager=None):
if isinstance(lookfor, int):
@@ -340,6 +346,24 @@ def getDamagePattern(lookfor, eager=None):
raise TypeError("Need integer or string as argument")
return pattern
+@cachedQuery(TargetResists, 1, "lookfor")
+def getTargetResists(lookfor, eager=None):
+ if isinstance(lookfor, int):
+ if eager is None:
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).get(lookfor)
+ else:
+ eager = processEager(eager)
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.ID == lookfor).first()
+ elif isinstance(lookfor, basestring):
+ eager = processEager(eager)
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.name == lookfor).first()
+ else:
+ raise TypeError("Need integer or string as argument")
+ return pattern
+
def searchFits(nameLike, where=None, eager=None):
if not isinstance(nameLike, basestring):
raise TypeError("Need string as argument")
@@ -361,7 +385,7 @@ def getSquadsIDsWithFitID(fitID):
return squads
else:
raise TypeError("Need integer as argument")
-
+
def getProjectedFits(fitID):
if isinstance(fitID, int):
with sd_lock:
@@ -369,7 +393,7 @@ def getProjectedFits(fitID):
fits = saveddata_session.query(Fit).filter(filter).all()
return fits
else:
- raise TypeError("Need integer as argument")
+ raise TypeError("Need integer as argument")
def add(stuff):
with sd_lock:
diff --git a/eos/db/saveddata/targetResists.py b/eos/db/saveddata/targetResists.py
new file mode 100644
index 000000000..f7106fe45
--- /dev/null
+++ b/eos/db/saveddata/targetResists.py
@@ -0,0 +1,35 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+from sqlalchemy import Table, Column, Integer, Float, ForeignKey, String
+from sqlalchemy.orm import mapper
+
+from eos.db import saveddata_meta
+from eos.types import TargetResists
+
+targetResists_table = Table("targetResists", saveddata_meta,
+ Column("ID", Integer, primary_key = True),
+ Column("name", String),
+ Column("emAmount", Float),
+ Column("thermalAmount", Float),
+ Column("kineticAmount", Float),
+ Column("explosiveAmount", Float),
+ Column("ownerID", ForeignKey("users.ID"), nullable=True))
+
+mapper(TargetResists, targetResists_table)
diff --git a/eos/saveddata/damagePattern.py b/eos/saveddata/damagePattern.py
index de70acef3..f9be89746 100644
--- a/eos/saveddata/damagePattern.py
+++ b/eos/saveddata/damagePattern.py
@@ -69,26 +69,44 @@ class DamagePattern(object):
def importPatterns(cls, text):
lines = re.split('[\n\r]+', text)
patterns = []
+ numPatterns = 0
for line in lines:
- line = line.split('#',1)[0] # allows for comments
- name, data = line.rsplit('=',1)
- name, data = name.strip(), ''.join(data.split()) # whitespace
+ try:
+ if line.strip()[0] == "#": # comments
+ continue
+ line = line.split('#',1)[0] # allows for comments
+ type, data = line.rsplit('=',1)
+ type, data = type.strip(), data.split(',')
+ except:
+ # Data isn't in correct format, continue to next line
+ continue
+ if type != "DamageProfile":
+ continue
+
+ numPatterns += 1
+ name, data = data[0], data[1:5]
fields = {}
- for entry in data.split(','):
- key, val = entry.split(':')
- fields["%sAmount" % cls.importMap[key.lower()]] = float(val)
- if len(fields) > 0: # Avoid possible blank lines
+ for index, val in enumerate(data):
+ try:
+ fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
+ except:
+ continue
+
+ if len(fields) == 4: # Avoid possible blank lines
pattern = DamagePattern(**fields)
- pattern.name = name
+ pattern.name = name.strip()
patterns.append(pattern)
- return patterns
- EXPORT_FORMAT = "%s = EM:%d, Therm:%d, Kin:%d, Exp:%d\n"
+ return patterns, numPatterns
+
+ EXPORT_FORMAT = "DamageProfile = %s,%d,%d,%d,%d\n"
@classmethod
def exportPatterns(cls, *patterns):
- out = ""
+ out = "# Exported from pyfa\n#\n"
+ out += "# Values are in following format:\n"
+ out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
for dp in patterns:
out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py
index bd5812c4b..3e959bec3 100644
--- a/eos/saveddata/drone.py
+++ b/eos/saveddata/drone.py
@@ -22,7 +22,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
- DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage")
+ DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
@@ -111,6 +111,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def dps(self):
+ return self.damageStats()
+
+ def damageStats(self, targetResists = None):
if self.__dps == None:
if self.dealsDamage is True and self.amountActive > 0:
if self.hasAmmo:
@@ -121,7 +124,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
getter = self.getModifiedItemAttr
cycleTime = self.getModifiedItemAttr(attr)
- volley = sum(map(lambda d: getter(d), self.DAMAGE_ATTRIBUTES)) * self.amountActive
+
+ volley = sum(map(lambda d: (getter("%sDamage"%d) or 0) * (1-getattr(targetResists, "%sAmount"%d, 0)), self.DAMAGE_TYPES))
+ volley *= self.amountActive
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
self.__dps = volley / (cycleTime / 1000.0)
else:
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index b1cd1449c..c26a55f2a 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -99,6 +99,17 @@ class Fit(object):
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
+ @property
+ def targetResists(self):
+ return self.__targetResists
+
+ @targetResists.setter
+ def targetResists(self, targetResists):
+ self.__targetResists = targetResists
+ self.__weaponDPS = None
+ self.__weaponVolley = None
+ self.__droneDPS = None
+
@property
def damagePattern(self):
return self.__damagePattern
@@ -811,12 +822,12 @@ class Fit(object):
weaponVolley = 0
for mod in self.modules:
- dps, volley = mod.damageStats
+ dps, volley = mod.damageStats(self.targetResists)
weaponDPS += dps
weaponVolley += volley
for drone in self.drones:
- droneDPS += drone.dps
+ droneDPS += drone.damageStats(self.targetResists)
self.__weaponDPS = weaponDPS
self.__weaponVolley = weaponVolley
@@ -838,6 +849,7 @@ class Fit(object):
copy.ship = deepcopy(self.ship, memo)
copy.name = "%s copy" % self.name
copy.damagePattern = self.damagePattern
+ copy.targetResists = self.targetResists
toCopy = ("modules", "drones", "cargo", "implants", "boosters", "projectedModules", "projectedDrones")
for name in toCopy:
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index 2e68816c3..b7d7e1315 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -44,7 +44,7 @@ class Hardpoint(Enum):
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""An instance of this class represents a module together with its charge and modified attributes"""
- DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage")
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
MINING_ATTRIBUTES = ("miningAmount", )
def __init__(self, item):
@@ -308,29 +308,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes.clear()
- @property
- def damageStats(self):
+ def damageStats(self, targetResists):
if self.__dps == None:
- if self.isEmpty:
- self.__dps = 0
- self.__volley = 0
- else:
- if self.state >= State.ACTIVE:
- if self.charge:
- volley = sum(map(lambda attr: self.getModifiedChargeAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
- else:
- volley = sum(map(lambda attr: self.getModifiedItemAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
- volley *= self.getModifiedItemAttr("damageMultiplier") or 1
- if volley:
- cycleTime = self.cycleTime
- self.__volley = volley
- self.__dps = volley / (cycleTime / 1000.0)
- else:
- self.__volley = 0
- self.__dps = 0
+ self.__dps = 0
+ self.__volley = 0
+
+ if not self.isEmpty and self.state >= State.ACTIVE:
+ if self.charge:
+ func = self.getModifiedChargeAttr
else:
- self.__volley = 0
- self.__dps = 0
+ func = self.getModifiedItemAttr
+
+ volley = sum(map(lambda attr: (func("%sDamage"%attr) or 0) * (1-getattr(targetResists, "%sAmount"%attr, 0)), self.DAMAGE_TYPES))
+ volley *= self.getModifiedItemAttr("damageMultiplier") or 1
+ if volley:
+ cycleTime = self.cycleTime
+ self.__volley = volley
+ self.__dps = volley / (cycleTime / 1000.0)
return self.__dps, self.__volley
@@ -354,11 +348,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def dps(self):
- return self.damageStats[0]
+ return self.damageStats(None)[0]
@property
def volley(self):
- return self.damageStats[1]
+ return self.damageStats(None)[1]
@property
def reloadTime(self):
diff --git a/eos/saveddata/targetResists.py b/eos/saveddata/targetResists.py
new file mode 100644
index 000000000..8798ebf19
--- /dev/null
+++ b/eos/saveddata/targetResists.py
@@ -0,0 +1,84 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+import re
+
+class TargetResists(object):
+ # also determined import/export order - VERY IMPORTANT
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+
+ def __init__(self, emAmount = 0, thermalAmount = 0, kineticAmount = 0, explosiveAmount = 0):
+ self.emAmount = emAmount
+ self.thermalAmount = thermalAmount
+ self.kineticAmount = kineticAmount
+ self.explosiveAmount = explosiveAmount
+
+ @classmethod
+ def importPatterns(cls, text):
+ lines = re.split('[\n\r]+', text)
+ patterns = []
+ numPatterns = 0
+ for line in lines:
+ try:
+ if line.strip()[0] == "#": # comments
+ continue
+ line = line.split('#',1)[0] # allows for comments
+ type, data = line.rsplit('=',1)
+ type, data = type.strip(), data.split(',')
+ except:
+ # Data isn't in correct format, continue to next line
+ continue
+
+ if type != "TargetResists":
+ continue
+
+ numPatterns += 1
+ name, data = data[0], data[1:5]
+ fields = {}
+
+ for index, val in enumerate(data):
+ val = float(val)
+ try:
+ assert 0 <= val <= 100
+ fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val/100
+ except:
+ continue
+
+ if len(fields) == 4: # Avoid possible blank lines
+ pattern = TargetResists(**fields)
+ pattern.name = name.strip()
+ patterns.append(pattern)
+
+ return patterns, numPatterns
+
+ EXPORT_FORMAT = "TargetResists = %s,%.1f,%.1f,%.1f,%.1f\n"
+ @classmethod
+ def exportPatterns(cls, *patterns):
+ out = "# Exported from pyfa\n#\n"
+ out += "# Values are in following format:\n"
+ out += "# TargetResists = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %]\n\n"
+ for dp in patterns:
+ out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount*100, dp.thermalAmount*100, dp.kineticAmount*100, dp.explosiveAmount*100)
+
+ return out.strip()
+
+ def __deepcopy__(self, memo):
+ p = TargetResists(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
+ p.name = "%s copy" % self.name
+ return p
diff --git a/eos/types.py b/eos/types.py
index 9896e526a..3e5223f4b 100644
--- a/eos/types.py
+++ b/eos/types.py
@@ -22,6 +22,7 @@ MetaGroup, AttributeInfo, Unit, EffectInfo, MetaType, MetaData, Traits
from eos.saveddata.price import Price
from eos.saveddata.user import User
from eos.saveddata.damagePattern import DamagePattern
+from eos.saveddata.targetResists import TargetResists
from eos.saveddata.character import Character, Skill
from eos.saveddata.module import Module, State, Slot, Hardpoint, Rack
from eos.saveddata.drone import Drone
diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py
index 4f93b8424..bd385255d 100644
--- a/gui/builtinContextMenus/__init__.py
+++ b/gui/builtinContextMenus/__init__.py
@@ -1,2 +1,3 @@
__all__ = ["moduleAmmoPicker", "itemStats", "damagePattern", "marketJump", "droneSplit", "itemRemove",
- "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo", "shipJump"]
+ "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo", "shipJump",
+ "targetResists"]
diff --git a/gui/builtinContextMenus/changeAffectingSkills.py b/gui/builtinContextMenus/changeAffectingSkills.py
index 4d1f5fdd4..5748ce78a 100644
--- a/gui/builtinContextMenus/changeAffectingSkills.py
+++ b/gui/builtinContextMenus/changeAffectingSkills.py
@@ -67,7 +67,7 @@ class ChangeAffectingSkills(ContextMenu):
rootMenu.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
return menuItem
- def getSubMenu(self, context, selection, menu, i):
+ def getSubMenu(self, context, selection, menu, i, pitem):
self.context = context
self.skillIds = {}
diff --git a/gui/builtinContextMenus/damagePattern.py b/gui/builtinContextMenus/damagePattern.py
index 1cde95a54..f76388629 100644
--- a/gui/builtinContextMenus/damagePattern.py
+++ b/gui/builtinContextMenus/damagePattern.py
@@ -13,35 +13,92 @@ class DamagePattern(ContextMenu):
return srcContext in ("resistancesViewFull",) and self.mainFrame.getActiveFit() is not None
def getText(self, itmContext, selection):
- sDP = service.DamagePattern.getInstance()
- self.patterns = sDP.getDamagePatternList()
- self.patterns.sort( key=lambda p: (p.name in ["Selected Ammo",
- "Uniform"], p.name) )
- m = map(lambda p: p.name, self.patterns)
- return m
-
- def activate(self, fullContext, selection, i):
sDP = service.DamagePattern.getInstance()
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
- sFit.setDamagePattern(fitID, self.patterns[i])
- setattr(self.mainFrame,"_activeDmgPattern",self.patterns[i])
+ self.fit = sFit.getFit(fitID)
+
+ self.patterns = sDP.getDamagePatternList()
+ self.patterns.sort( key=lambda p: (p.name not in ["Uniform",
+ "Selected Ammo"], p.name) )
+
+ self.patternIds = {}
+ self.subMenus = {}
+ self.singles = []
+
+ # iterate and separate damage patterns based on "[Parent] Child"
+ for pattern in self.patterns:
+ start, end = pattern.name.find('['), pattern.name.find(']')
+ if start is not -1 and end is not -1:
+ currBase = pattern.name[start+1:end]
+ # set helper attr
+ setattr(pattern, "_name", pattern.name[end+1:].strip())
+ if currBase not in self.subMenus:
+ self.subMenus[currBase] = []
+ self.subMenus[currBase].append(pattern)
+ else:
+ self.singles.append(pattern)
+
+ # return list of names, with singles first followed by submenu names
+ self.m = map(lambda p: p.name, self.singles) + self.subMenus.keys()
+ return self.m
+
+ def handlePatternSwitch(self, event):
+ pattern = self.patternIds.get(event.Id, False)
+ if pattern is False:
+ event.Skip()
+ return
+
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+ sFit.setDamagePattern(fitID, pattern)
+ setattr(self.mainFrame,"_activeDmgPattern", pattern)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
- def getBitmap(self, context, selection):
+ def addPattern(self, menu, pattern):
+ id = wx.NewId()
+ name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
+
+ self.patternIds[id] = pattern
+ item = wx.MenuItem(menu, id, name)
+ menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, item)
+
+ # set pattern attr to menu item
+ item.pattern = pattern
+
+ # determine active pattern
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
f = sFit.getFit(fitID)
dp = f.damagePattern
- if dp is None:
+
+ if dp == pattern:
+ bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
+ item.SetBitmap(bitmap)
+ return item
+
+ def getSubMenu(self, context, selection, menu, i, pitem):
+ menu.Bind(wx.EVT_MENU, self.handlePatternSwitch) # this bit is required for some reason
+
+ if self.m[i] not in self.subMenus:
+ # if we're trying to get submenu to something that shouldn't have one,
+ # redirect event of the item to handlePatternSwitch and put pattern in
+ # our patternIds mapping, then return None for no submenu
+ id = pitem.GetId()
+ self.patternIds[id] = self.singles[i]
+ menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
+ if self.patternIds[id] == self.fit.damagePattern:
+ bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
+ pitem.SetBitmap(bitmap)
return None
- index = self.patterns.index(dp)
- bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
- l = [None] * len(self.patterns)
- l[index] = bitmap
+ sub = wx.Menu()
+ sub.Bind(wx.EVT_MENU, self.handlePatternSwitch)
- return l
+ # Items that have a parent
+ for pattern in self.subMenus[self.m[i]]:
+ sub.AppendItem(self.addPattern(sub, pattern))
+ return sub
DamagePattern.register()
diff --git a/gui/builtinContextMenus/moduleAmmoPicker.py b/gui/builtinContextMenus/moduleAmmoPicker.py
index c19c9d7ab..55b03cf8b 100644
--- a/gui/builtinContextMenus/moduleAmmoPicker.py
+++ b/gui/builtinContextMenus/moduleAmmoPicker.py
@@ -116,7 +116,7 @@ class ModuleAmmoPicker(ContextMenu):
m.Append(id, u'─ %s ─' % text)
m.Enable(id, False)
- def getSubMenu(self, context, selection, menu, i):
+ def getSubMenu(self, context, selection, menu, i, pitem):
self.context = context
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
m = wx.Menu()
diff --git a/gui/builtinContextMenus/targetResists.py b/gui/builtinContextMenus/targetResists.py
new file mode 100644
index 000000000..c7180a88f
--- /dev/null
+++ b/gui/builtinContextMenus/targetResists.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+from gui.contextMenu import ContextMenu
+import gui.mainFrame
+import service
+import gui.globalEvents as GE
+import wx
+from gui import bitmapLoader
+
+class TargetResists(ContextMenu):
+ def __init__(self):
+ self.mainFrame = gui.mainFrame.MainFrame.getInstance()
+
+ def display(self, srcContext, selection):
+ if self.mainFrame.getActiveFit() is None or srcContext not in ("firepowerViewFull",):
+ return False
+
+ sTR = service.TargetResists.getInstance()
+ self.patterns = sTR.getTargetResistsList()
+ self.patterns.sort( key=lambda p: (p.name in ["None"], p.name) )
+
+ return len(self.patterns) > 0
+
+ def getText(self, itmContext, selection):
+ return "Target Resists"
+
+ def activate(self, fullContext, selection, i):
+ pass
+
+ def handleResistSwitch(self, event):
+ pattern = self.patternIds.get(event.Id, False)
+ if pattern is False:
+ event.Skip()
+ return
+
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+ sFit.setTargetResists(fitID, pattern)
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
+
+ def addPattern(self, menu, pattern, currBase = None):
+ id = wx.NewId()
+ name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
+
+ self.patternIds[id] = pattern
+ item = wx.MenuItem(menu, id, name)
+ # set pattern attr to menu item
+ item.pattern = pattern
+
+ # determine active pattern
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+ f = sFit.getFit(fitID)
+ tr = f.targetResists
+
+ if tr == pattern:
+ bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
+ item.SetBitmap(bitmap)
+ return item
+
+ def addSeperator(self, m, text):
+ id = wx.NewId()
+ m.Append(id, u'─ %s ─' % text)
+ m.Enable(id, False)
+
+ def getSubMenu(self, context, selection, menu, i, pitem):
+ self.context = context
+ menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
+ m = wx.Menu()
+ m.Bind(wx.EVT_MENU, self.handleResistSwitch)
+ self.patternIds = {}
+
+ self.subMenus = {}
+ self.singles = []
+
+ for pattern in self.patterns:
+ start, end = pattern.name.find('['), pattern.name.find(']')
+ if start is not -1 and end is not -1:
+ currBase = pattern.name[start+1:end]
+ # set helper attr
+ setattr(pattern, "_name", pattern.name[end+1:].strip())
+ if currBase not in self.subMenus:
+ self.subMenus[currBase] = []
+ self.subMenus[currBase].append(pattern)
+ else:
+ self.singles.append(pattern)
+
+ m.AppendItem(self.addPattern(m, None)) # Add reset
+ m.AppendSeparator()
+
+ # Single items, no parent
+ for pattern in self.singles:
+ m.AppendItem(self.addPattern(m, pattern))
+
+ # Items that have a parent
+ for menuName, patterns in self.subMenus.items():
+ # Create parent item for root menu that is simply name of parent
+ item = wx.MenuItem(menu, wx.NewId(), menuName)
+
+ # Create menu for child items
+ sub = wx.Menu()
+ sub.Bind(wx.EVT_MENU, self.handleResistSwitch)
+
+ # Apply child menu to parent item
+ item.SetSubMenu(sub)
+
+ # Append child items to child menu
+ for pattern in patterns:
+ sub.AppendItem(self.addPattern(sub, pattern))
+ m.AppendItem(item) #finally, append parent item to root menu
+
+ return m
+
+TargetResists.register()
diff --git a/gui/builtinContextMenus/whProjector.py b/gui/builtinContextMenus/whProjector.py
index 146139aa8..fb32f34a8 100644
--- a/gui/builtinContextMenus/whProjector.py
+++ b/gui/builtinContextMenus/whProjector.py
@@ -17,7 +17,7 @@ class WhProjector(ContextMenu):
def activate(self, fullContext, selection, i):
pass
- def getSubMenu(self, context, selection, menu, i):
+ def getSubMenu(self, context, selection, menu, i, pitem):
self.idmap = {}
menu.Bind(wx.EVT_MENU, self.handleSelection)
m = wx.Menu()
diff --git a/gui/builtinStatsViews/firepowerViewFull.py b/gui/builtinStatsViews/firepowerViewFull.py
index 1b26b2662..7f18b6a0c 100644
--- a/gui/builtinStatsViews/firepowerViewFull.py
+++ b/gui/builtinStatsViews/firepowerViewFull.py
@@ -40,13 +40,20 @@ class FirepowerViewFull(StatsView):
def populatePanel(self, contentPanel, headerPanel):
contentSizer = contentPanel.GetSizer()
parent = self.panel = contentPanel
+
self.headerPanel = headerPanel
+ headerContentSizer = wx.BoxSizer(wx.HORIZONTAL)
+ hsizer = headerPanel.GetSizer()
+ hsizer.Add(headerContentSizer,0,0,0)
+ self.stEff = wx.StaticText(headerPanel, wx.ID_ANY, "( Effective )")
+ headerContentSizer.Add(self.stEff)
+ headerPanel.GetParent().AddToggleItem(self.stEff)
panel = "full"
sizerFirepower = wx.FlexGridSizer(1, 4)
sizerFirepower.AddGrowableCol(1)
-
+
contentSizer.Add( sizerFirepower, 0, wx.EXPAND, 0)
counter = 0
@@ -129,6 +136,10 @@ class FirepowerViewFull(StatsView):
def refreshPanel(self, fit):
#If we did anything intresting, we'd update our labels to reflect the new fit's stats here
+ if fit is not None and fit.targetResists is not None:
+ self.stEff.Show()
+ else:
+ self.stEff.Hide()
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS",None),
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
diff --git a/gui/contextMenu.py b/gui/contextMenu.py
index 9f0a8d7bd..9c6213d63 100644
--- a/gui/contextMenu.py
+++ b/gui/contextMenu.py
@@ -54,7 +54,7 @@ class ContextMenu(object):
item = wx.MenuItem(menu, id, text)
menu.info[id] = (m, fullContext, it)
- sub = m.getSubMenu(srcContext, selection, menu, it)
+ sub = m.getSubMenu(srcContext, selection, menu, it, item)
if sub is not None:
item.SetSubMenu(sub)
@@ -95,7 +95,7 @@ class ContextMenu(object):
def activate(self, context, selection, i):
return None
- def getSubMenu(self, context, selection, menu, i):
+ def getSubMenu(self, context, selection, menu, i, pitem):
return None
def getText(self, context, selection):
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 8607aac06..72754af29 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -46,6 +46,7 @@ from gui.shipBrowser import ShipBrowser, FitSelected
from gui.characterEditor import CharacterEditor
from gui.characterSelection import CharacterSelection
from gui.patternEditor import DmgPatternEditorDlg
+from gui.resistsEditor import ResistsEditorDlg
from gui.preferenceDialog import PreferenceDialog
from gui.graphFrame import GraphFrame
from gui.copySelectDialog import CopySelectDialog
@@ -311,6 +312,11 @@ class MainFrame(wx.Frame):
dlg=CharacterEditor(self)
dlg.Show()
+ def showTargetResistsEditor(self, event):
+ dlg=ResistsEditorDlg(self)
+ dlg.ShowModal()
+ dlg.Destroy()
+
def showDamagePatternEditor(self, event):
dlg=DmgPatternEditorDlg(self)
dlg.ShowModal()
@@ -392,6 +398,8 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.showCharacterEditor, id=menuBar.characterEditorId)
# Damage pattern editor
self.Bind(wx.EVT_MENU, self.showDamagePatternEditor, id=menuBar.damagePatternEditorId)
+ # Target Resists editor
+ self.Bind(wx.EVT_MENU, self.showTargetResistsEditor, id=menuBar.targetResistsEditorId)
# Import dialog
self.Bind(wx.EVT_MENU, self.showImportDialog, id=wx.ID_OPEN)
# Export dialog
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 73577578d..06e1dfabf 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -28,6 +28,7 @@ class MainMenuBar(wx.MenuBar):
def __init__(self):
self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId()
+ self.targetResistsEditorId = wx.NewId()
self.graphFrameId = wx.NewId()
self.backupFitsId = wx.NewId()
self.exportSkillsNeededId = wx.NewId()
@@ -82,6 +83,10 @@ class MainMenuBar(wx.MenuBar):
damagePatternEditItem.SetBitmap(bitmapLoader.getBitmap("damagePattern_small", "icons"))
windowMenu.AppendItem(damagePatternEditItem)
+ targetResistsEditItem = wx.MenuItem(windowMenu, self.targetResistsEditorId, "Target Resists Editor\tCTRL+R")
+ targetResistsEditItem.SetBitmap(bitmapLoader.getBitmap("explosive_big", "icons"))
+ windowMenu.AppendItem(targetResistsEditItem)
+
graphFrameItem = wx.MenuItem(windowMenu, self.graphFrameId, "Graphs\tCTRL+G")
graphFrameItem.SetBitmap(bitmapLoader.getBitmap("graphs_small", "icons"))
windowMenu.AppendItem(graphFrameItem)
diff --git a/gui/patternEditor.py b/gui/patternEditor.py
index 013fe11f6..f3b8da855 100644
--- a/gui/patternEditor.py
+++ b/gui/patternEditor.py
@@ -22,12 +22,14 @@ import bitmapLoader
import service
from wx.lib.intctrl import IntCtrl
from gui.utils.clipboard import toClipboard, fromClipboard
+from service.damagePattern import ImportError
###########################################################################
## Class DmgPatternEditorDlg
###########################################################################
class DmgPatternEditorDlg (wx.Dialog):
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
def __init__(self, parent):
wx.Dialog.__init__ (self, parent, id = wx.ID_ANY, title = u"Damage Pattern Editor", size = wx.Size( 400,240 ))
@@ -93,34 +95,37 @@ class DmgPatternEditorDlg (wx.Dialog):
self.kinbitmap = bitmapLoader.getBitmap("kinetic_big", "icons")
self.expbitmap = bitmapLoader.getBitmap("explosive_big", "icons")
- dmgeditSizer = wx.FlexGridSizer(2, 4, 0, 2)
+ dmgeditSizer = wx.FlexGridSizer(2, 6, 0, 2)
dmgeditSizer.AddGrowableCol(0)
- dmgeditSizer.AddGrowableCol(3)
+ dmgeditSizer.AddGrowableCol(5)
dmgeditSizer.SetFlexibleDirection(wx.BOTH)
dmgeditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
width = -1
defSize = wx.Size(width,-1)
- self.bmpEM = wx.StaticBitmap(self, wx.ID_ANY, self.embitmap)
- dmgeditSizer.Add(self.bmpEM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)
- self.editEm = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize)
- dmgeditSizer.Add(self.editEm, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ for i, type in enumerate(self.DAMAGE_TYPES):
+ bmp = wx.StaticBitmap(self, wx.ID_ANY, bitmapLoader.getBitmap("%s_big"%type, "icons"))
+ if i%2:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
+ border = 10
+ else:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
+ border = 5
- self.bmpTHERM = wx.StaticBitmap(self, wx.ID_ANY, self.thermbitmap)
- dmgeditSizer.Add(self.bmpTHERM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25)
- self.editThermal = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0)
- dmgeditSizer.Add(self.editThermal, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ # set text edit
+ setattr(self, "%sEdit"%type, IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize))
+ setattr(self, "%sPerc"%type, wx.StaticText(self, wx.ID_ANY, u"0%"))
+ editObj = getattr(self, "%sEdit"%type)
- self.bmpKIN = wx.StaticBitmap(self, wx.ID_ANY, self.kinbitmap)
- dmgeditSizer.Add(self.bmpKIN, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)
- self.editKinetic = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize)
- dmgeditSizer.Add(self.editKinetic, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ dmgeditSizer.Add(bmp, 0, style, border)
+ dmgeditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ dmgeditSizer.Add(getattr(self, "%sPerc"%type), 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5)
- self.bmpEXP = wx.StaticBitmap(self, wx.ID_ANY, self.expbitmap)
- dmgeditSizer.Add(self.bmpEXP, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25)
- self.editExplosive = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0)
- dmgeditSizer.Add(self.editExplosive, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated)
+ editObj.SetLimited(True)
+ editObj.SetMin(0)
+ editObj.SetMax(99999)
contentSizer.Add(dmgeditSizer, 1, wx.EXPAND | wx.ALL, 5)
self.slfooter = wx.StaticLine(self)
@@ -129,9 +134,9 @@ class DmgPatternEditorDlg (wx.Dialog):
footerSizer = wx.BoxSizer(wx.HORIZONTAL)
perSizer = wx.BoxSizer(wx.VERTICAL)
- self.stPercentages = wx.StaticText(self, wx.ID_ANY, u"")
- self.stPercentages.Wrap(-1)
- perSizer.Add(self.stPercentages, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
+ self.stNotice = wx.StaticText(self, wx.ID_ANY, u"")
+ self.stNotice.Wrap(-1)
+ perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
footerSizer.Add(perSizer, 1, wx.ALIGN_CENTER_VERTICAL, 5)
@@ -167,22 +172,6 @@ class DmgPatternEditorDlg (wx.Dialog):
bsize = self.GetBestSize()
self.SetSize((-1,bsize.height))
- self.editEm.SetLimited(True)
- self.editThermal.SetLimited(True)
- self.editKinetic.SetLimited(True)
- self.editExplosive.SetLimited(True)
-
- self.editEm.SetMin(0)
- self.editThermal.SetMin(0)
- self.editKinetic.SetMin(0)
- self.editExplosive.SetMin(0)
-
- self.editEm.SetMax(99999)
- self.editThermal.SetMax(99999)
- self.editKinetic.SetMax(99999)
- self.editExplosive.SetMax(99999)
-
-
self.new.Bind(wx.EVT_BUTTON, self.newPattern)
self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
@@ -190,11 +179,6 @@ class DmgPatternEditorDlg (wx.Dialog):
self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
- self.editEm.Bind(wx.EVT_TEXT, self.ValuesUpdated)
- self.editThermal.Bind(wx.EVT_TEXT, self.ValuesUpdated)
- self.editKinetic.Bind(wx.EVT_TEXT, self.ValuesUpdated)
- self.editExplosive.Bind(wx.EVT_TEXT, self.ValuesUpdated)
-
self.patternChanged()
def closeEvent(self, event):
@@ -205,18 +189,13 @@ class DmgPatternEditorDlg (wx.Dialog):
return
p = self.getActivePattern()
- p.emAmount = self._EM = self.editEm.GetValue()
- p.thermalAmount = self._THERM = self.editThermal.GetValue()
- p.kineticAmount = self._KIN = self.editKinetic.GetValue()
- p.explosiveAmount = self._EXP = self.editExplosive.GetValue()
- total = self._EM + self._THERM + self._KIN + self._EXP
- format = "EM: %d%%, THERM: %d%%, KIN: %d%%, EXP: %d%%"
- if total > 0:
- ltext = format %(self._EM*100/total, self._THERM*100/total, self._KIN*100/total, self._EXP*100/total)
- else:
- ltext = format %(0, 0, 0, 0)
+ total = sum(map(lambda attr: getattr(self, "%sEdit"%attr).GetValue(), self.DAMAGE_TYPES))
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ percObj = getattr(self, "%sPerc"%type)
+ setattr(p, "%sAmount"%type, editObj.GetValue())
+ percObj.SetLabel("%.1f%%"%(float(editObj.GetValue())*100/total if total > 0 else 0))
- self.stPercentages.SetLabel(ltext)
self.totSizer.Layout()
if event is not None:
@@ -225,18 +204,16 @@ class DmgPatternEditorDlg (wx.Dialog):
service.DamagePattern.getInstance().saveChanges(p)
def restrict(self):
- self.editEm.Enable(False)
- self.editExplosive.Enable(False)
- self.editKinetic.Enable(False)
- self.editThermal.Enable(False)
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable(False)
self.rename.Enable(False)
self.delete.Enable(False)
def unrestrict(self):
- self.editEm.Enable()
- self.editExplosive.Enable()
- self.editKinetic.Enable()
- self.editThermal.Enable()
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable()
self.rename.Enable()
self.delete.Enable()
@@ -248,6 +225,7 @@ class DmgPatternEditorDlg (wx.Dialog):
def patternChanged(self, event=None):
p = self.getActivePattern()
+
if p is None:
return
@@ -257,8 +235,9 @@ class DmgPatternEditorDlg (wx.Dialog):
self.unrestrict()
self.block = True
- for field in ("em", "thermal", "kinetic", "explosive"):
- edit = getattr(self, "edit%s" % field.capitalize())
+
+ for field in self.DAMAGE_TYPES:
+ edit = getattr(self, "%sEdit" % field)
amount = getattr(p, "%sAmount" % field)
edit.SetValue(amount)
@@ -271,6 +250,13 @@ class DmgPatternEditorDlg (wx.Dialog):
self.choices.append(p)
id = self.ccDmgPattern.Append(p.name)
self.ccDmgPattern.SetSelection(id)
+
+ self.restrict()
+ # reset values
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.SetValue(0)
+
self.btnSave.SetLabel("Create")
self.renamePattern()
@@ -296,16 +282,16 @@ class DmgPatternEditorDlg (wx.Dialog):
def processRename(self, event):
newName = self.namePicker.GetLineText(0)
- self.stPercentages.SetLabel("")
+ self.stNotice.SetLabel("")
p = self.getActivePattern()
for pattern in self.choices:
if pattern.name == newName and p != pattern:
- self.stPercentages.SetLabel("Name already used, please pick another")
+ self.stNotice.SetLabel("Name already used, please choose another")
return
if newName == "":
- self.stPercentages.SetLabel("Invalid name.")
+ self.stNotice.SetLabel("Invalid name.")
return
sDP = service.DamagePattern.getInstance()
@@ -349,16 +335,42 @@ class DmgPatternEditorDlg (wx.Dialog):
def __del__( self ):
pass
+ def updateChoices(self):
+ "Gathers list of patterns and updates choice selections"
+ sDP = service.DamagePattern.getInstance()
+ self.choices = sDP.getDamagePatternList()
+
+ for dp in self.choices:
+ if dp.name == "Selected Ammo": # don't include this special butterfly
+ self.choices.remove(dp)
+
+ # Sort the remaining list and continue on
+ self.choices.sort(key=lambda p: p.name)
+ self.ccDmgPattern.Clear()
+
+ for choice in map(lambda p: p.name, self.choices):
+ self.ccDmgPattern.Append(choice)
+
+ self.ccDmgPattern.SetSelection(0)
+ self.patternChanged()
+
def importPatterns(self, event):
text = fromClipboard()
if text:
sDP = service.DamagePattern.getInstance()
- sDP.importPatterns(text)
- self.stPercentages.SetLabel("Patterns imported from clipboard")
+ try:
+ sDP.importPatterns(text)
+ self.stNotice.SetLabel("Patterns successfully imported from clipboard")
+ except service.damagePattern.ImportError, e:
+ self.stNotice.SetLabel(str(e))
+ except Exception, e:
+ self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
+ finally:
+ self.updateChoices()
else:
- self.stPercentages.SetLabel("Could not import from clipboard")
+ self.stNotice.SetLabel("Could not import from clipboard")
def exportPatterns(self, event):
sDP = service.DamagePattern.getInstance()
toClipboard( sDP.exportPatterns() )
- self.stPercentages.SetLabel("Patterns exported to clipboard")
+ self.stNotice.SetLabel("Patterns exported to clipboard")
diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py
new file mode 100644
index 000000000..1092e77a2
--- /dev/null
+++ b/gui/resistsEditor.py
@@ -0,0 +1,407 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+#===============================================================================
+
+import wx
+import bitmapLoader
+import service
+from gui.utils.clipboard import toClipboard, fromClipboard
+from service.targetResists import ImportError
+
+class ResistsEditorDlg (wx.Dialog):
+
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+
+ def __init__(self, parent):
+ wx.Dialog.__init__ (self, parent, id = wx.ID_ANY, title = u"Target Resists Editor", size = wx.Size( 350,240 ))
+
+ self.block = False
+ self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ sTR = service.TargetResists.getInstance()
+
+ self.choices = sTR.getTargetResistsList()
+
+ # Sort the remaining list and continue on
+ self.choices.sort(key=lambda p: p.name)
+ self.ccResists = wx.Choice(self, choices=map(lambda p: p.name, self.choices))
+ self.ccResists.Bind(wx.EVT_CHOICE, self.patternChanged)
+ self.ccResists.SetSelection(0)
+
+ self.namePicker = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
+ self.namePicker.Bind(wx.EVT_TEXT_ENTER, self.processRename)
+ self.namePicker.Hide()
+
+ self.btnSave = wx.Button(self, wx.ID_SAVE)
+ self.btnSave.Hide()
+ self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
+
+ size = None
+ headerSizer.Add(self.ccResists, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
+
+ buttons = (("new", wx.ART_NEW),
+ ("rename", bitmapLoader.getBitmap("rename", "icons")),
+ ("copy", wx.ART_COPY),
+ ("delete", wx.ART_DELETE))
+ for name, art in buttons:
+ bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
+ btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
+ if size is None:
+ size = btn.GetSize()
+
+ btn.SetMinSize(size)
+ btn.SetMaxSize(size)
+
+ btn.Layout()
+ setattr(self, name, btn)
+ btn.Enable(True)
+ btn.SetToolTipString("%s resist profile" % name.capitalize())
+ headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
+
+ self.sl = wx.StaticLine(self)
+ mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+
+ resistEditSizer = wx.FlexGridSizer(2, 6, 0, 2)
+ resistEditSizer.AddGrowableCol(0)
+ resistEditSizer.AddGrowableCol(5)
+ resistEditSizer.SetFlexibleDirection(wx.BOTH)
+ resistEditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
+
+ width = -1
+ defSize = wx.Size(50,-1)
+
+ for i, type in enumerate(self.DAMAGE_TYPES):
+ if i%2:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
+ border = 25
+ else:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
+ border = 5
+
+ bmp = wx.StaticBitmap(self, wx.ID_ANY, bitmapLoader.getBitmap("%s_big"%type, "icons"))
+ resistEditSizer.Add(bmp, 0, style, border)
+ # set text edit
+ setattr(self, "%sEdit"%type, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize))
+ editObj = getattr(self, "%sEdit"%type)
+ resistEditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ resistEditSizer.Add(wx.StaticText( self, wx.ID_ANY, u"%", wx.DefaultPosition, wx.DefaultSize, 0 ), 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated)
+
+ contentSizer.Add(resistEditSizer, 1, wx.EXPAND | wx.ALL, 5)
+ self.slfooter = wx.StaticLine(self)
+ contentSizer.Add(self.slfooter, 0, wx.EXPAND | wx.TOP, 5)
+
+ footerSizer = wx.BoxSizer(wx.HORIZONTAL)
+ perSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.stNotice = wx.StaticText(self, wx.ID_ANY, u"")
+ self.stNotice.Wrap(-1)
+ perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
+
+ footerSizer.Add(perSizer, 1, wx.ALIGN_CENTER_VERTICAL, 5)
+
+ self.totSizer = wx.BoxSizer(wx.VERTICAL)
+
+ contentSizer.Add(footerSizer, 0, wx.EXPAND, 5)
+
+ mainSizer.Add(contentSizer, 1, wx.EXPAND, 0)
+
+ if "wxGTK" in wx.PlatformInfo:
+ self.closeBtn = wx.Button( self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0 )
+ mainSizer.Add( self.closeBtn, 0, wx.ALL|wx.ALIGN_RIGHT, 5 )
+ self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent)
+
+ self.SetSizer(mainSizer)
+
+ importExport = (("Import", wx.ART_FILE_OPEN, "from"),
+ ("Export", wx.ART_FILE_SAVE_AS, "to"))
+
+ for name, art, direction in importExport:
+ bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
+ btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
+
+ btn.SetMinSize( btn.GetSize() )
+ btn.SetMaxSize( btn.GetSize() )
+
+ btn.Layout()
+ setattr(self, name, btn)
+ btn.Enable(True)
+ btn.SetToolTipString("%s patterns %s clipboard" % (name, direction) )
+ footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
+
+ self.Layout()
+ bsize = self.GetBestSize()
+ self.SetSize((-1,bsize.height))
+
+ self.new.Bind(wx.EVT_BUTTON, self.newPattern)
+ self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
+ self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
+ self.delete.Bind(wx.EVT_BUTTON, self.deletePattern)
+ self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
+ self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
+
+ self.patternChanged()
+
+ def closeEvent(self, event):
+ self.Destroy()
+
+ def ValuesUpdated(self, event=None):
+ '''
+ Event that is fired when resists values change. Iterates through all
+ resist edit fields. If blank, sets it to 0.0. If it is not a proper
+ decimal value, sets text color to red and refuses to save changes until
+ issue is resolved
+ '''
+ if self.block:
+ return
+
+ try:
+ p = self.getActivePattern()
+
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+
+ if editObj.GetValue() == "":
+ # if we are blank, overwrite with 0
+ editObj.ChangeValue("0.0")
+ editObj.SetInsertionPointEnd()
+
+ value = float(editObj.GetValue())
+
+ # assertion, because they're easy
+ assert 0 <= value <= 100
+
+ # if everything checks out, set resist attribute
+ setattr(p, "%sAmount"%type, value/100)
+
+ self.totSizer.Layout()
+
+ if event is not None:
+ # If we get here, everything is normal. Reset color
+ event.EventObject.SetForegroundColour(wx.NullColor)
+ event.Skip()
+
+ service.TargetResists.getInstance().saveChanges(p)
+
+ except ValueError:
+ event.EventObject.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Formatting (decimals only)")
+ except AssertionError:
+ event.EventObject.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Range (must be 0-100)")
+ finally: # Refresh for color changes to take effect immediately
+ self.Refresh()
+
+ def restrict(self):
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable(False)
+ self.rename.Enable(False)
+ self.delete.Enable(False)
+
+ def unrestrict(self):
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable()
+ self.rename.Enable()
+ self.delete.Enable()
+
+ def getActivePattern(self):
+ if len(self.choices) == 0:
+ return None
+
+ return self.choices[self.ccResists.GetSelection()]
+
+ def patternChanged(self, event=None):
+ "Event fired when user selects pattern. Can also be called from script"
+ p = self.getActivePattern()
+ if p is None:
+ # This happens when there are no patterns in the DB. As such, force
+ # user to create one first or exit dlg.
+ self.newPattern(None)
+ return
+
+ self.block = True
+ # Set new values
+ for field in self.DAMAGE_TYPES:
+ edit = getattr(self, "%sEdit" % field)
+ amount = getattr(p, "%sAmount" % field)*100
+ edit.ChangeValue(str(amount))
+
+ self.block = False
+ self.ValuesUpdated()
+
+ def newPattern(self, event):
+ '''
+ Simply does new-pattern specifics: replaces label on button, restricts,
+ and resets values to default. Hands off to the rename function for
+ further handling.
+ '''
+ self.btnSave.SetLabel("Create")
+ self.restrict()
+ # reset values
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.ChangeValue("0.0")
+ editObj.SetForegroundColour(wx.NullColor)
+
+ self.Refresh()
+ self.renamePattern()
+
+ def renamePattern(self, event=None):
+ "Changes layout to facilitate naming a pattern"
+
+ self.ccResists.Hide()
+ self.namePicker.Show()
+ self.headerSizer.Replace(self.ccResists, self.namePicker)
+ self.namePicker.SetFocus()
+ if event is not None: # Rename mode
+ self.btnSave.SetLabel("Rename")
+ self.namePicker.SetValue(self.getActivePattern().name)
+ else: # Create mode
+ self.namePicker.SetValue("")
+
+ for btn in (self.new, self.rename, self.delete, self.copy):
+ btn.Hide()
+ self.headerSizer.Remove(btn)
+
+ self.headerSizer.Add(self.btnSave, 0, wx.ALIGN_CENTER)
+ self.btnSave.Show()
+ self.headerSizer.Layout()
+ if event is not None:
+ event.Skip()
+
+ def processRename(self, event):
+ '''
+ Processes rename event (which can be new or old patterns). If new
+ pattern, creates it; if old, selects it. if checks are valid, rename
+ saves pattern to DB.
+
+ Also resets to default layout and unrestricts.
+ '''
+ newName = self.namePicker.GetLineText(0)
+ self.stNotice.SetLabel("")
+
+ if newName == "":
+ self.stNotice.SetLabel("Invalid name")
+ return
+
+ sTR = service.TargetResists.getInstance()
+ if event.EventObject.Label == "Create":
+ p = sTR.newPattern()
+ else:
+ # we are renaming, so get the current selection
+ p = self.getActivePattern()
+
+ # test for patterns of the same name
+ for pattern in self.choices:
+ if pattern.name == newName and p != pattern:
+ self.stNotice.SetLabel("Name already used, please choose another")
+ return
+
+ # rename regardless of new or rename
+ sTR.renamePattern(p, newName)
+
+ self.updateChoices(newName)
+ self.headerSizer.Replace(self.namePicker, self.ccResists)
+ self.ccResists.Show()
+ self.namePicker.Hide()
+ self.btnSave.Hide()
+ self.headerSizer.Remove(self.btnSave)
+ for btn in (self.new, self.rename, self.delete, self.copy):
+ self.headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+ btn.Show()
+
+ sel = self.ccResists.GetSelection()
+ self.ValuesUpdated()
+ self.unrestrict()
+
+ def copyPattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ p = sTR.copyPattern(self.getActivePattern())
+ self.choices.append(p)
+ id = self.ccResists.Append(p.name)
+ self.ccResists.SetSelection(id)
+ self.btnSave.SetLabel("Copy")
+ self.renamePattern()
+ self.patternChanged()
+
+ def deletePattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ sel = self.ccResists.GetSelection()
+ sTR.deletePattern(self.getActivePattern())
+ self.ccResists.Delete(sel)
+ self.ccResists.SetSelection(max(0, sel - 1))
+ del self.choices[sel]
+ self.patternChanged()
+
+ def __del__( self ):
+ pass
+
+ def updateChoices(self, select=None):
+ "Gathers list of patterns and updates choice selections"
+ sTR = service.TargetResists.getInstance()
+ self.choices = sTR.getTargetResistsList()
+
+ # Sort the remaining list and continue on
+ self.choices.sort(key=lambda p: p.name)
+ self.ccResists.Clear()
+
+ for i, choice in enumerate(map(lambda p: p.name, self.choices)):
+ self.ccResists.Append(choice)
+
+ if select is not None and choice == select:
+ self.ccResists.SetSelection(i)
+
+ if select is None:
+ self.ccResists.SetSelection(0)
+
+ self.patternChanged()
+
+ def importPatterns(self, event):
+ "Event fired when import from clipboard button is clicked"
+
+ text = fromClipboard()
+ if text:
+ sTR = service.TargetResists.getInstance()
+ try:
+ sTR.importPatterns(text)
+ self.stNotice.SetLabel("Patterns successfully imported from clipboard")
+ except service.targetResists.ImportError, e:
+ self.stNotice.SetLabel(str(e))
+ except Exception, e:
+ self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
+ finally:
+ self.updateChoices()
+ else:
+ self.stNotice.SetLabel("Could not import from clipboard")
+
+ def exportPatterns(self, event):
+ "Event fired when export to clipboard button is clicked"
+
+ sTR = service.TargetResists.getInstance()
+ toClipboard( sTR.exportPatterns() )
+ self.stNotice.SetLabel("Patterns exported to clipboard")
diff --git a/service/__init__.py b/service/__init__.py
index 8ded8a66a..8e9e9f6f5 100644
--- a/service/__init__.py
+++ b/service/__init__.py
@@ -3,6 +3,7 @@ from service.fit import Fit
from service.attribute import Attribute
from service.character import Character
from service.damagePattern import DamagePattern
+from service.targetResists import TargetResists
from service.settings import SettingsProvider
from service.fleet import Fleet
from service.update import Update
diff --git a/service/damagePattern.py b/service/damagePattern.py
index f5c1ebd13..374940e0c 100644
--- a/service/damagePattern.py
+++ b/service/damagePattern.py
@@ -21,6 +21,9 @@ import eos.db
import eos.types
import copy
+class ImportError(Exception):
+ pass
+
class DamagePattern():
instance = None
@classmethod
@@ -68,17 +71,21 @@ class DamagePattern():
current = self.getDamagePatternList()
for pattern in current:
lookup[pattern.name] = pattern
- try:
- imports = eos.types.DamagePattern.importPatterns(text)
- for pattern in imports:
- if pattern.name in lookup:
- match = lookup[pattern.name]
- match.__dict__.update(pattern.__dict__)
- else:
- eos.db.save(pattern)
- eos.db.commit()
- except:
- pass
+
+ imports, num = eos.types.DamagePattern.importPatterns(text)
+ for pattern in imports:
+ if pattern.name in lookup:
+ match = lookup[pattern.name]
+ match.__dict__.update(pattern.__dict__)
+ else:
+ eos.db.save(pattern)
+ eos.db.commit()
+
+ lenImports = len(imports)
+ if lenImports == 0:
+ raise ImportError("No patterns found for import")
+ if lenImports != num:
+ raise ImportError("%d patterns imported from clipboard; %d had errors"%(num, num-lenImports))
def exportPatterns(self):
patterns = self.getDamagePatternList()
diff --git a/service/fit.py b/service/fit.py
index f6f747cf2..60e899faf 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -83,6 +83,7 @@ class Fit(object):
def __init__(self):
self.pattern = DamagePattern.getInstance().getDamagePattern("Uniform")
+ self.targetResists = None
self.character = Character.getInstance().all5()
self.booster = False
self.dirtyFitIDs = set()
@@ -148,6 +149,7 @@ class Fit(object):
fit.ship = eos.types.Ship(eos.db.getItem(shipID))
fit.name = name if name is not None else "New %s" % fit.ship.item.name
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
fit.character = self.character
fit.booster = self.booster
eos.db.save(fit)
@@ -683,6 +685,23 @@ class Fit(object):
self.recalc(fit)
+ def getTargetResists(self, fitID):
+ if fitID is None:
+ return
+
+ fit = eos.db.getFit(fitID)
+ return fit.targetResists
+
+ def setTargetResists(self, fitID, pattern):
+ if fitID is None:
+ return
+
+ fit = eos.db.getFit(fitID)
+ fit.targetResists = pattern
+ eos.db.commit()
+
+ self.recalc(fit)
+
def getDamagePattern(self, fitID):
if fitID is None:
return
@@ -760,6 +779,7 @@ class Fit(object):
for fit in fits:
fit.character = self.character
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
return fits
def importFitFromBuffer(self, bufferStr, activeFit=None):
@@ -767,6 +787,7 @@ class Fit(object):
for fit in fits:
fit.character = self.character
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
return fits
def saveImportedFits(self, fits):
diff --git a/service/targetResists.py b/service/targetResists.py
new file mode 100644
index 000000000..a8f61b9f9
--- /dev/null
+++ b/service/targetResists.py
@@ -0,0 +1,87 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+#===============================================================================
+
+import eos.db
+import eos.types
+import copy
+
+class ImportError(Exception):
+ pass
+
+class TargetResists():
+ instance = None
+ @classmethod
+ def getInstance(cls):
+ if cls.instance is None:
+ cls.instance = TargetResists()
+
+ return cls.instance
+
+ def getTargetResistsList(self):
+ return eos.db.getTargetResistsList()
+
+ def getTargetResists(self, name):
+ return eos.db.getTargetResists(name)
+
+ def newPattern(self):
+ p = eos.types.TargetResists(0.0, 0.0, 0.0, 0.0)
+ p.name = ""
+ return p
+
+ def renamePattern(self, p, newName):
+ p.name = newName
+ eos.db.save(p)
+
+ def deletePattern(self, p):
+ eos.db.remove(p)
+
+ def copyPattern(self, p):
+ newP = copy.deepcopy(p)
+ eos.db.save(newP)
+ return newP
+
+ def saveChanges(self, p):
+ eos.db.save(p)
+
+ def importPatterns(self, text):
+ lookup = {}
+ current = self.getTargetResistsList()
+ for pattern in current:
+ lookup[pattern.name] = pattern
+
+ imports, num = eos.types.TargetResists.importPatterns(text)
+ for pattern in imports:
+ if pattern.name in lookup:
+ match = lookup[pattern.name]
+ match.__dict__.update(pattern.__dict__)
+ else:
+ eos.db.save(pattern)
+ eos.db.commit()
+
+ lenImports = len(imports)
+ if lenImports == 0:
+ raise ImportError("No patterns found for import")
+ if lenImports != num:
+ raise ImportError("%d patterns imported from clipboard; %d had errors"%(num, num-lenImports))
+
+ def exportPatterns(self):
+ patterns = self.getTargetResistsList()
+ patterns.sort(key=lambda p: p.name)
+ return eos.types.TargetResists.exportPatterns(*patterns)
+