From 3390c06b5da63927084deb51f773828034657d3e Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Thu, 13 Jun 2013 05:19:02 -0700 Subject: [PATCH 1/4] Able to export XML that works with EVEMon --- gui/characterSelection.py | 27 ++++++++---- gui/mainFrame.py | 24 +++++++++++ gui/mainMenuBar.py | 3 ++ service/character.py | 86 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 8 deletions(-) diff --git a/gui/characterSelection.py b/gui/characterSelection.py index d522d4c25..3fd678f1e 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -109,25 +109,33 @@ class CharacterSelection(wx.Panel): def fitChanged(self, event): self.charChoice.Enable(event.fitID != None) - choice = self.charChoice cFit = service.Fit.getInstance() currCharID = choice.GetClientData(choice.GetCurrentSelection()) fit = cFit.getFit(event.fitID) newCharID = fit.character.ID if fit is not None else None + print newCharID if event.fitID is None: + print "** No active fit **" self.skillReqsStaticBitmap.SetBitmap(self.cleanSkills) - self.skillReqsStaticBitmap.SetToolTipString("No active fit.") + self.skillReqsStaticBitmap.SetToolTipString("No active fit") else: sCharacter = service.Character.getInstance() reqs = sCharacter.checkRequirements(fit) + sCharacter.skillReqsDict = {'charname':fit.character.name, 'skills':[]} + print "-- Skills required for character \"%s\":" % (fit.character.name,) if len(reqs) == 0: - tip = "All prerequisites have been met" + print " -- ** All skill prerequisites have been met **" + tip = "All skill prerequisites have been met" self.skillReqsStaticBitmap.SetBitmap(self.greenSkills) else: - tip = self._buildSkillsTooltip(reqs) + tip = "Skills required:\n" + tip += self._buildSkillsTooltip(reqs) self.skillReqsStaticBitmap.SetBitmap(self.redSkills) self.skillReqsStaticBitmap.SetToolTipString(tip.strip()) + print "" + print sCharacter.skillReqsDict + print "" if newCharID == None: cChar = service.Character.getInstance() @@ -139,14 +147,19 @@ class CharacterSelection(wx.Panel): def _buildSkillsTooltip(self, reqs, tabulationLevel = 0): tip = "" + sCharacter = service.Character.getInstance() if tabulationLevel == 0: for item, subReqs in reqs.iteritems(): - tip += "%s:\n" % item.name + print " -- {%5d} %s" % (item.ID, item.name) + tip += " %s:\n" % item.name tip += self._buildSkillsTooltip(subReqs, 1) else: for name, info in reqs.iteritems(): - level, more = info - tip += "%s%s: %d\n" % (" " * tabulationLevel, name, level) + level, ID, more = info + print " %s{%5d} %s: %d" % (" " * tabulationLevel, ID, name, level) + sCharacter.skillReqsDict['skills'].append( + {'skillID' : ID, 'skill' : name, 'level' : int(level)}) + tip += " %s%s: %d\n" % (" " * tabulationLevel, name, level) tip += self._buildSkillsTooltip(more, tabulationLevel + 1) return tip diff --git a/gui/mainFrame.py b/gui/mainFrame.py index a3b5030e9..c06b4c609 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -335,6 +335,8 @@ class MainFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.importFromClipboard, id=wx.ID_PASTE) # Backup fits self.Bind(wx.EVT_MENU, self.backupToXml, id=menuBar.backupFitsId) + # Export skills needed + self.Bind(wx.EVT_MENU, self.exportSkillsNeeded, id=menuBar.exportSkillsNeededId) # Preference dialog self.Bind(wx.EVT_MENU, self.showPreferenceDialog, id = menuBar.preferencesId) @@ -475,6 +477,28 @@ class MainFrame(wx.Frame): saveDialog.Destroy() + def exportSkillsNeeded(self, event): + sCharacter = service.Character.getInstance() + saveDialog = wx.FileDialog( + self, + "Export Skills Needed As...", + wildcard = "EVEMon skills training file (*.xml)|*.xml|Text skills training file (*.txt)|*.txt", + style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + if (saveDialog.ShowModal() == wx.ID_OK): + saveFmtInt = saveDialog.GetFilterIndex() + saveFmt = "" + if saveFmtInt == 0: # Per ordering of wildcards above + saveFmt = "xml" + else: + saveFmt = "txt" + print("User selected format %d \'%s\'" % (saveFmtInt, saveFmt)) + filePath = saveDialog.GetPath() + self.waitDialog = animUtils.WaitDialog(self) + sCharacter.backupSkills(filePath, saveFmt, self.closeWaitDialog) + self.waitDialog.ShowModal() + + saveDialog.Destroy() + def closeWaitDialog(self): self.waitDialog.Destroy() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 6404242e3..327bc0e60 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -30,6 +30,7 @@ class MainMenuBar(wx.MenuBar): self.damagePatternEditorId = wx.NewId() self.graphFrameId = wx.NewId() self.backupFitsId = wx.NewId() + self.exportSkillsNeededId = wx.NewId() self.preferencesId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -47,6 +48,7 @@ class MainMenuBar(wx.MenuBar): fileMenu.Append(self.backupFitsId, "&Backup fits", "Backup all fittings to a XML file") fileMenu.Append(wx.ID_OPEN, "&Import\tCTRL+O", "Import a fit into pyfa.") fileMenu.Append(wx.ID_SAVEAS, "&Export\tCTRL+S", "Export the fit to another format.") + fileMenu.Append(self.exportSkillsNeededId, "Export &Skills Needed", "Export skills needed for this fitting") fileMenu.AppendSeparator() fileMenu.Append(wx.ID_EXIT) @@ -105,5 +107,6 @@ class MainMenuBar(wx.MenuBar): enable = event.fitID is not None self.Enable(wx.ID_SAVEAS, enable) self.Enable(wx.ID_COPY, enable) + self.Enable(self.exportSkillsNeededId, enable) event.Skip() diff --git a/service/character.py b/service/character.py index f97a1daa7..755159f18 100644 --- a/service/character.py +++ b/service/character.py @@ -23,8 +23,48 @@ import copy import service import itertools +import os.path +import locale +import threading +import wx +from codecs import open + +from xml.etree import ElementTree +from xml.dom import minidom + +import gui.mainFrame + +EVEMON_COMPATIBLE_VERSION = "4081" + +class SkillBackupThread(threading.Thread): + def __init__(self, path, saveFmt, callback): + threading.Thread.__init__(self) + self.path = path + self.saveFmt = saveFmt + self.callback = callback + + def run(self): + path = self.path + mainFrame = gui.mainFrame.MainFrame.getInstance() + sCharacter = Character.getInstance() + sFit = service.Fit.getInstance() + fit = sFit.getFit(mainFrame.getActiveFit()) + backupFile = open(path, "w", encoding="utf-8") + backupData = ""; + if self.saveFmt == "xml": + backupData = sCharacter.exportXml() + elif self.saveFmt == "txt": + backupData = sCharacter.exportText() + else: + backupData = sCharacter.exportText() + backupFile.write(backupData) + backupFile.close() + wx.CallAfter(self.callback) + class Character(): instance = None + skillReqsDict = {} + @classmethod def getInstance(cls): if cls.instance is None: @@ -32,6 +72,49 @@ class Character(): return cls.instance + def exportText(self): + data = "" + + mySkills = repr(self.skillReqsDict) + data += "-" * 79 + data += '\n' + data += repr(self.skillReqsDict) + data += '\n' + data += "-" * 79 + data += '\n' + + return data + + def exportXml(self): + root = ElementTree.Element("plan") + root.attrib["name"] = "Pyfa exported plan for "+self.skillReqsDict['charname'] + root.attrib["revision"] = EVEMON_COMPATIBLE_VERSION + + sorts = ElementTree.SubElement(root, "sorting") + sorts.attrib["criteria"] = "None" + sorts.attrib["order"] = "None" + sorts.attrib["groupByPriority"] = "false" + + for s in self.skillReqsDict['skills']: + entry = ElementTree.SubElement(root, "entry") + entry.attrib["skillID"] = str(s["skillID"]) + entry.attrib["skill"] = s["skill"] + entry.attrib["level"] = str(s["level"]) + entry.attrib["priority"] = "3" + entry.attrib["type"] = "Prerequisite" + notes = ElementTree.SubElement(entry, "notes") + notes.text = entry.attrib["skill"] + + tree = ElementTree.ElementTree(root) + data = ElementTree.tostring(root, 'utf-8') + prettydata = minidom.parseString(data).toprettyxml(indent=" ") + + return prettydata + + def backupSkills(self, path, saveFmt, callback): + thread = SkillBackupThread(path, saveFmt, callback) + thread.start() + def all0(self): all0 = eos.types.Character.getAll0() eos.db.commit() @@ -169,10 +252,11 @@ class Character(): def _checkRequirements(self, fit, char, subThing, reqs): for req, level in subThing.requiredSkills.iteritems(): name = req.name + ID = req.ID info = reqs.get(name) currLevel, subs = info if info is not None else 0, {} if level > currLevel and (char is None or char.getSkill(req).level < level): - reqs[name] = (level, subs) + reqs[name] = (level, ID, subs) self._checkRequirements(fit, char, req, subs) return reqs From 961fbaef695ed05dce685739fb923adaab3be3a0 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Sat, 15 Jun 2013 16:29:26 -0700 Subject: [PATCH 2/4] Fix broken commit with better parameter passing; cleanup --- gui/characterSelection.py | 9 --------- gui/mainFrame.py | 3 +-- service/character.py | 12 +++++------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/gui/characterSelection.py b/gui/characterSelection.py index 3fd678f1e..5fdddb539 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -114,18 +114,14 @@ class CharacterSelection(wx.Panel): currCharID = choice.GetClientData(choice.GetCurrentSelection()) fit = cFit.getFit(event.fitID) newCharID = fit.character.ID if fit is not None else None - print newCharID if event.fitID is None: - print "** No active fit **" self.skillReqsStaticBitmap.SetBitmap(self.cleanSkills) self.skillReqsStaticBitmap.SetToolTipString("No active fit") else: sCharacter = service.Character.getInstance() reqs = sCharacter.checkRequirements(fit) sCharacter.skillReqsDict = {'charname':fit.character.name, 'skills':[]} - print "-- Skills required for character \"%s\":" % (fit.character.name,) if len(reqs) == 0: - print " -- ** All skill prerequisites have been met **" tip = "All skill prerequisites have been met" self.skillReqsStaticBitmap.SetBitmap(self.greenSkills) else: @@ -133,9 +129,6 @@ class CharacterSelection(wx.Panel): tip += self._buildSkillsTooltip(reqs) self.skillReqsStaticBitmap.SetBitmap(self.redSkills) self.skillReqsStaticBitmap.SetToolTipString(tip.strip()) - print "" - print sCharacter.skillReqsDict - print "" if newCharID == None: cChar = service.Character.getInstance() @@ -150,13 +143,11 @@ class CharacterSelection(wx.Panel): sCharacter = service.Character.getInstance() if tabulationLevel == 0: for item, subReqs in reqs.iteritems(): - print " -- {%5d} %s" % (item.ID, item.name) tip += " %s:\n" % item.name tip += self._buildSkillsTooltip(subReqs, 1) else: for name, info in reqs.iteritems(): level, ID, more = info - print " %s{%5d} %s: %d" % (" " * tabulationLevel, ID, name, level) sCharacter.skillReqsDict['skills'].append( {'skillID' : ID, 'skill' : name, 'level' : int(level)}) tip += " %s%s: %d\n" % (" " * tabulationLevel, name, level) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index c06b4c609..73924dfd2 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -491,10 +491,9 @@ class MainFrame(wx.Frame): saveFmt = "xml" else: saveFmt = "txt" - print("User selected format %d \'%s\'" % (saveFmtInt, saveFmt)) filePath = saveDialog.GetPath() self.waitDialog = animUtils.WaitDialog(self) - sCharacter.backupSkills(filePath, saveFmt, self.closeWaitDialog) + sCharacter.backupSkills(filePath, saveFmt, self.getActiveFit(), self.closeWaitDialog) self.waitDialog.ShowModal() saveDialog.Destroy() diff --git a/service/character.py b/service/character.py index 755159f18..bff153d80 100644 --- a/service/character.py +++ b/service/character.py @@ -32,23 +32,21 @@ from codecs import open from xml.etree import ElementTree from xml.dom import minidom -import gui.mainFrame - EVEMON_COMPATIBLE_VERSION = "4081" class SkillBackupThread(threading.Thread): - def __init__(self, path, saveFmt, callback): + def __init__(self, path, saveFmt, activeFit, callback): threading.Thread.__init__(self) self.path = path self.saveFmt = saveFmt + self.activeFit = activeFit self.callback = callback def run(self): path = self.path - mainFrame = gui.mainFrame.MainFrame.getInstance() sCharacter = Character.getInstance() sFit = service.Fit.getInstance() - fit = sFit.getFit(mainFrame.getActiveFit()) + fit = sFit.getFit(self.activeFit) backupFile = open(path, "w", encoding="utf-8") backupData = ""; if self.saveFmt == "xml": @@ -111,8 +109,8 @@ class Character(): return prettydata - def backupSkills(self, path, saveFmt, callback): - thread = SkillBackupThread(path, saveFmt, callback) + def backupSkills(self, path, saveFmt, activeFit, callback): + thread = SkillBackupThread(path, saveFmt, activeFit, callback) thread.start() def all0(self): From 67ed8818d88395b7c5e1def1ff1bd28710fb0c53 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Sat, 15 Jun 2013 17:50:53 -0700 Subject: [PATCH 3/4] Cleaned up. Supports txt, xml and emp (gzipped xml) --- gui/characterSelection.py | 15 ++++++++++----- gui/mainFrame.py | 10 ++++++++-- service/character.py | 39 +++++++++++++++++++++++---------------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/gui/characterSelection.py b/gui/characterSelection.py index 5fdddb539..3d20d5589 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -138,19 +138,24 @@ class CharacterSelection(wx.Panel): event.Skip() - def _buildSkillsTooltip(self, reqs, tabulationLevel = 0): + def _buildSkillsTooltip(self, reqs, currItem = "", tabulationLevel = 0): tip = "" sCharacter = service.Character.getInstance() if tabulationLevel == 0: for item, subReqs in reqs.iteritems(): tip += " %s:\n" % item.name - tip += self._buildSkillsTooltip(subReqs, 1) + tip += self._buildSkillsTooltip(subReqs, item.name, 1) else: for name, info in reqs.iteritems(): level, ID, more = info - sCharacter.skillReqsDict['skills'].append( - {'skillID' : ID, 'skill' : name, 'level' : int(level)}) + sCharacter.skillReqsDict['skills'].append({ + 'item' : currItem, + 'skillID' : ID, + 'skill' : name, + 'level' : level, + 'indent' : tabulationLevel + }) tip += " %s%s: %d\n" % (" " * tabulationLevel, name, level) - tip += self._buildSkillsTooltip(more, tabulationLevel + 1) + tip += self._buildSkillsTooltip(more, currItem, tabulationLevel + 1) return tip diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 73924dfd2..0f4f2d8d8 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -263,7 +263,9 @@ class MainFrame(wx.Frame): dlg=wx.FileDialog( self, "Open One Or More Fitting Files", - wildcard = "EFT text fitting files (*.cfg)|*.cfg|EvE XML fitting files (*.xml)|*.xml|All Files (*)|*", + wildcard = "EFT text fitting files (*.cfg)|*.cfg|" \ + "EvE XML fitting files (*.xml)|*.xml|" \ + "All Files (*)|*", style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE) if (dlg.ShowModal() == wx.ID_OK): self.waitDialog = animUtils.WaitDialog(self, title = "Importing") @@ -482,12 +484,16 @@ class MainFrame(wx.Frame): saveDialog = wx.FileDialog( self, "Export Skills Needed As...", - wildcard = "EVEMon skills training file (*.xml)|*.xml|Text skills training file (*.txt)|*.txt", + wildcard = "EVEMon skills training file (*.emp)|*.emp|" \ + "EVEMon skills training XML file (*.xml)|*.xml|" \ + "Text skills training file (*.txt)|*.txt", style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if (saveDialog.ShowModal() == wx.ID_OK): saveFmtInt = saveDialog.GetFilterIndex() saveFmt = "" if saveFmtInt == 0: # Per ordering of wildcards above + saveFmt = "emp" + elif saveFmtInt == 1: saveFmt = "xml" else: saveFmt = "txt" diff --git a/service/character.py b/service/character.py index bff153d80..87f4f17b2 100644 --- a/service/character.py +++ b/service/character.py @@ -32,6 +32,8 @@ from codecs import open from xml.etree import ElementTree from xml.dom import minidom +import gzip + EVEMON_COMPATIBLE_VERSION = "4081" class SkillBackupThread(threading.Thread): @@ -47,16 +49,19 @@ class SkillBackupThread(threading.Thread): sCharacter = Character.getInstance() sFit = service.Fit.getInstance() fit = sFit.getFit(self.activeFit) - backupFile = open(path, "w", encoding="utf-8") backupData = ""; - if self.saveFmt == "xml": + if self.saveFmt == "xml" or self.saveFmt == "emp": backupData = sCharacter.exportXml() - elif self.saveFmt == "txt": - backupData = sCharacter.exportText() else: backupData = sCharacter.exportText() - backupFile.write(backupData) - backupFile.close() + + if self.saveFmt == "emp": + with gzip.open(path, "wb") as backupFile: + backupFile.write(backupData) + else: + with open(path, "w", encoding="utf-8") as backupFile: + backupFile.write(backupData) + wx.CallAfter(self.callback) class Character(): @@ -71,15 +76,17 @@ class Character(): return cls.instance def exportText(self): - data = "" - - mySkills = repr(self.skillReqsDict) - data += "-" * 79 - data += '\n' - data += repr(self.skillReqsDict) - data += '\n' - data += "-" * 79 - data += '\n' + data = "Pyfa exported plan for \""+self.skillReqsDict['charname']+"\"\n" + data += "=" * 79 + "\n" + data += "\n" + item = "" + for s in self.skillReqsDict['skills']: + if item == "" or not item == s["item"]: + item = s["item"] + data += "-" * 79 + "\n" + data += "Skills required for {}:\n".format(item) + data += "{}{}: {}\n".format(" " * s["indent"], s["skill"], int(s["level"])) + data += "-" * 79 + "\n" return data @@ -97,7 +104,7 @@ class Character(): entry = ElementTree.SubElement(root, "entry") entry.attrib["skillID"] = str(s["skillID"]) entry.attrib["skill"] = s["skill"] - entry.attrib["level"] = str(s["level"]) + entry.attrib["level"] = str(int(s["level"])) entry.attrib["priority"] = "3" entry.attrib["type"] = "Prerequisite" notes = ElementTree.SubElement(entry, "notes") From fa64ed904d2808e66b979899053b3bda21a01cc5 Mon Sep 17 00:00:00 2001 From: Martin Falatic Date: Thu, 20 Jun 2013 00:44:19 -0700 Subject: [PATCH 4/4] Fixed skills exporter to remove duplicates that confuse EVEMon --- service/character.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/service/character.py b/service/character.py index 87f4f17b2..97c55ec0a 100644 --- a/service/character.py +++ b/service/character.py @@ -99,17 +99,24 @@ class Character(): sorts.attrib["criteria"] = "None" sorts.attrib["order"] = "None" sorts.attrib["groupByPriority"] = "false" - - for s in self.skillReqsDict['skills']: - entry = ElementTree.SubElement(root, "entry") - entry.attrib["skillID"] = str(s["skillID"]) - entry.attrib["skill"] = s["skill"] - entry.attrib["level"] = str(int(s["level"])) - entry.attrib["priority"] = "3" - entry.attrib["type"] = "Prerequisite" - notes = ElementTree.SubElement(entry, "notes") - notes.text = entry.attrib["skill"] + skillsSeen = set() + + for s in self.skillReqsDict['skills']: + skillKey = str(s["skillID"])+"::"+s["skill"]+"::"+str(int(s["level"])) + if skillKey in skillsSeen: + pass # Duplicate skills confuse EVEMon + else: + skillsSeen.add(skillKey) + entry = ElementTree.SubElement(root, "entry") + entry.attrib["skillID"] = str(s["skillID"]) + entry.attrib["skill"] = s["skill"] + entry.attrib["level"] = str(int(s["level"])) + entry.attrib["priority"] = "3" + entry.attrib["type"] = "Prerequisite" + notes = ElementTree.SubElement(entry, "notes") + notes.text = entry.attrib["skill"] + tree = ElementTree.ElementTree(root) data = ElementTree.tostring(root, 'utf-8') prettydata = minidom.parseString(data).toprettyxml(indent=" ")