Compare commits
4 Commits
v2.65.2.22
...
v2.65.2.26
| Author | SHA1 | Date | |
|---|---|---|---|
| e119eeb14a | |||
| d8e6cc76c9 | |||
| bfd5bbb881 | |||
| c64991fb59 |
@@ -159,11 +159,24 @@ class CargoView(d.Display):
|
|||||||
else:
|
else:
|
||||||
dstCargoItemID = None
|
dstCargoItemID = None
|
||||||
|
|
||||||
self.mainFrame.command.Submit(cmd.GuiLocalModuleToCargoCommand(
|
modifiers = wx.GetMouseState().GetModifiers()
|
||||||
|
isCopy = modifiers == wx.MOD_CONTROL
|
||||||
|
isBatch = modifiers == wx.MOD_SHIFT
|
||||||
|
if isBatch:
|
||||||
|
self.mainFrame.command.Submit(
|
||||||
|
cmd.GuiBatchLocalModuleToCargoCommand(
|
||||||
|
fitID=self.mainFrame.getActiveFit(), modPosition=modIdx, copy=isCopy
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.mainFrame.command.Submit(
|
||||||
|
cmd.GuiLocalModuleToCargoCommand(
|
||||||
fitID=self.mainFrame.getActiveFit(),
|
fitID=self.mainFrame.getActiveFit(),
|
||||||
modPosition=modIdx,
|
modPosition=modIdx,
|
||||||
cargoItemID=dstCargoItemID,
|
cargoItemID=dstCargoItemID,
|
||||||
copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
|
copy=isCopy,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def fitChanged(self, event):
|
def fitChanged(self, event):
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|||||||
@@ -9,6 +9,46 @@ from gui.utils.numberFormatter import formatAmount
|
|||||||
|
|
||||||
_t = wx.GetTranslation
|
_t = wx.GetTranslation
|
||||||
|
|
||||||
|
# Mapping of repair/transfer amount attributes to their duration attribute and display name
|
||||||
|
PER_SECOND_ATTRIBUTES = {
|
||||||
|
"armorDamageAmount": {
|
||||||
|
"durationAttr": "duration",
|
||||||
|
"displayName": "Armor Hitpoints Repaired per second",
|
||||||
|
"unit": "HP/s"
|
||||||
|
},
|
||||||
|
"shieldBonus": {
|
||||||
|
"durationAttr": "duration",
|
||||||
|
"displayName": "Shield Hitpoints Repaired per second",
|
||||||
|
"unit": "HP/s"
|
||||||
|
},
|
||||||
|
"powerTransferAmount": {
|
||||||
|
"durationAttr": "duration",
|
||||||
|
"displayName": "Capacitor Transferred per second",
|
||||||
|
"unit": "GJ/s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PerSecondAttributeInfo:
|
||||||
|
"""Helper class to store info about computed per-second attributes"""
|
||||||
|
def __init__(self, displayName, unit):
|
||||||
|
self.displayName = displayName
|
||||||
|
self.unit = PerSecondUnit(unit)
|
||||||
|
|
||||||
|
|
||||||
|
class PerSecondUnit:
|
||||||
|
"""Helper class to mimic the Unit class for per-second attributes"""
|
||||||
|
def __init__(self, displayName):
|
||||||
|
self.displayName = displayName
|
||||||
|
self.name = ""
|
||||||
|
|
||||||
|
|
||||||
|
class PerSecondAttributeValue:
|
||||||
|
"""Helper class to store computed per-second attribute values"""
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
self.info = None # Will be set when adding to attrs
|
||||||
|
|
||||||
|
|
||||||
def defaultSort(item):
|
def defaultSort(item):
|
||||||
return (item.metaLevel or 0, item.name)
|
return (item.metaLevel or 0, item.name)
|
||||||
@@ -36,8 +76,12 @@ class ItemCompare(wx.Panel):
|
|||||||
self.item = item
|
self.item = item
|
||||||
self.items = sorted(items, key=defaultSort)
|
self.items = sorted(items, key=defaultSort)
|
||||||
self.attrs = {}
|
self.attrs = {}
|
||||||
|
self.computedAttrs = {} # Store computed per-second attributes
|
||||||
self.HighlightOn = wx.Colour(255, 255, 0, wx.ALPHA_OPAQUE)
|
self.HighlightOn = wx.Colour(255, 255, 0, wx.ALPHA_OPAQUE)
|
||||||
self.highlightedNames = []
|
self.highlightedNames = []
|
||||||
|
self.bangBuckColumn = None # Store the column selected for bang/buck calculation
|
||||||
|
self.bangBuckColumnName = None # Store the display name of the selected column
|
||||||
|
self.columnHighlightColour = wx.Colour(173, 216, 230, wx.ALPHA_OPAQUE) # Light blue for column highlight
|
||||||
|
|
||||||
# get a dict of attrName: attrInfo of all unique attributes across all items
|
# get a dict of attrName: attrInfo of all unique attributes across all items
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
@@ -45,22 +89,65 @@ class ItemCompare(wx.Panel):
|
|||||||
if item.attributes[attr].info.displayName:
|
if item.attributes[attr].info.displayName:
|
||||||
self.attrs[attr] = item.attributes[attr].info
|
self.attrs[attr] = item.attributes[attr].info
|
||||||
|
|
||||||
|
# Compute per-second attributes for items that have both the amount and duration
|
||||||
|
for perSecondKey, config in PER_SECOND_ATTRIBUTES.items():
|
||||||
|
amountAttr = perSecondKey
|
||||||
|
durationAttr = config["durationAttr"]
|
||||||
|
perSecondAttrName = f"{perSecondKey}_per_second"
|
||||||
|
|
||||||
|
# Check if any item has both attributes
|
||||||
|
hasPerSecondAttr = False
|
||||||
|
for item in self.items:
|
||||||
|
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||||
|
hasPerSecondAttr = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if hasPerSecondAttr:
|
||||||
|
# Add the per-second attribute info to attrs
|
||||||
|
perSecondInfo = PerSecondAttributeInfo(config["displayName"], config["unit"])
|
||||||
|
self.attrs[perSecondAttrName] = perSecondInfo
|
||||||
|
self.computedAttrs[perSecondAttrName] = {
|
||||||
|
"amountAttr": amountAttr,
|
||||||
|
"durationAttr": durationAttr
|
||||||
|
}
|
||||||
|
|
||||||
# Process attributes for items and find ones that differ
|
# Process attributes for items and find ones that differ
|
||||||
for attr in list(self.attrs.keys()):
|
for attr in list(self.attrs.keys()):
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
# we can automatically break here if this item doesn't have the attribute,
|
# Check if this is a computed attribute
|
||||||
# as that means at least one item did
|
if attr in self.computedAttrs:
|
||||||
|
computed = self.computedAttrs[attr]
|
||||||
|
amountAttr = computed["amountAttr"]
|
||||||
|
durationAttr = computed["durationAttr"]
|
||||||
|
|
||||||
|
# Item needs both attributes to compute per-second value
|
||||||
|
if amountAttr not in item.attributes or durationAttr not in item.attributes:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Calculate per-second value
|
||||||
|
amountValue = item.attributes[amountAttr].value
|
||||||
|
durationValue = item.attributes[durationAttr].value
|
||||||
|
# Duration is in milliseconds, convert to seconds
|
||||||
|
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = perSecondValue
|
||||||
|
continue
|
||||||
|
|
||||||
|
if perSecondValue != value:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Regular attribute handling
|
||||||
if attr not in item.attributes:
|
if attr not in item.attributes:
|
||||||
break
|
break
|
||||||
|
|
||||||
# this is the first attribute for the item set, set the initial value
|
|
||||||
if value is None:
|
if value is None:
|
||||||
value = item.attributes[attr].value
|
value = item.attributes[attr].value
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if attr not in item.attributes or item.attributes[attr].value != value:
|
if item.attributes[attr].value != value:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# attribute values were all the same, delete
|
# attribute values were all the same, delete
|
||||||
@@ -89,6 +176,7 @@ class ItemCompare(wx.Panel):
|
|||||||
|
|
||||||
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
|
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
|
||||||
self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols)
|
self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols)
|
||||||
|
self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnRightClick)
|
||||||
|
|
||||||
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.HighlightRow)
|
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.HighlightRow)
|
||||||
|
|
||||||
@@ -105,6 +193,23 @@ class ItemCompare(wx.Panel):
|
|||||||
self.Thaw()
|
self.Thaw()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
def OnColumnRightClick(self, event):
|
||||||
|
column = event.GetColumn()
|
||||||
|
# Column 0 is "Item", column len(self.attrs) + 1 is "Price", len(self.attrs) + 2 is "Buck/bang"
|
||||||
|
# Only allow selecting attribute columns (1 to len(self.attrs))
|
||||||
|
if 1 <= column <= len(self.attrs):
|
||||||
|
# If clicking the same column, deselect it
|
||||||
|
if self.bangBuckColumn == column:
|
||||||
|
self.bangBuckColumn = None
|
||||||
|
self.bangBuckColumnName = None
|
||||||
|
else:
|
||||||
|
self.bangBuckColumn = column
|
||||||
|
# Get the display name of the selected column
|
||||||
|
attr_key = list(self.attrs.keys())[column - 1]
|
||||||
|
self.bangBuckColumnName = self.attrs[attr_key].displayName if self.attrs[attr_key].displayName else attr_key
|
||||||
|
self.UpdateList()
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def SortCompareCols(self, event):
|
def SortCompareCols(self, event):
|
||||||
self.Freeze()
|
self.Freeze()
|
||||||
self.paramList.ClearAll()
|
self.paramList.ClearAll()
|
||||||
@@ -148,12 +253,32 @@ class ItemCompare(wx.Panel):
|
|||||||
# Remember to reduce by 1, because the attrs array
|
# Remember to reduce by 1, because the attrs array
|
||||||
# starts at 0 while the list has the item name as column 0.
|
# starts at 0 while the list has the item name as column 0.
|
||||||
attr = str(list(self.attrs.keys())[sort - 1])
|
attr = str(list(self.attrs.keys())[sort - 1])
|
||||||
|
# Handle computed attributes for sorting
|
||||||
|
if attr in self.computedAttrs:
|
||||||
|
computed = self.computedAttrs[attr]
|
||||||
|
amountAttr = computed["amountAttr"]
|
||||||
|
durationAttr = computed["durationAttr"]
|
||||||
|
func = lambda _val: (_val.attributes[amountAttr].value / (_val.attributes[durationAttr].value / 1000.0)) if (amountAttr in _val.attributes and durationAttr in _val.attributes and _val.attributes[durationAttr].value > 0) else 0.0
|
||||||
|
else:
|
||||||
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
||||||
# Clicked on a column that's not part of our array (price most likely)
|
# Clicked on a column that's not part of our array (price most likely)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Price
|
# Price
|
||||||
if sort == len(self.attrs) + 1:
|
if sort == len(self.attrs) + 1:
|
||||||
func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
|
func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
|
||||||
|
# Buck/bang
|
||||||
|
elif sort == len(self.attrs) + 2:
|
||||||
|
if self.bangBuckColumn is not None:
|
||||||
|
attr_key = list(self.attrs.keys())[self.bangBuckColumn - 1]
|
||||||
|
if attr_key in self.computedAttrs:
|
||||||
|
computed = self.computedAttrs[attr_key]
|
||||||
|
amountAttr = computed["amountAttr"]
|
||||||
|
durationAttr = computed["durationAttr"]
|
||||||
|
func = lambda i: (i.price.price / (i.attributes[amountAttr].value / (i.attributes[durationAttr].value / 1000.0)) if (amountAttr in i.attributes and durationAttr in i.attributes and i.attributes[durationAttr].value > 0 and (i.attributes[amountAttr].value / (i.attributes[durationAttr].value / 1000.0)) > 0) else float("Inf"))
|
||||||
|
else:
|
||||||
|
func = lambda i: (i.price.price / i.attributes[attr_key].value if (attr_key in i.attributes and i.attributes[attr_key].value > 0) else float("Inf"))
|
||||||
|
else:
|
||||||
|
func = defaultSort
|
||||||
# Something else
|
# Something else
|
||||||
else:
|
else:
|
||||||
self.sortReverse = False
|
self.sortReverse = False
|
||||||
@@ -166,18 +291,49 @@ class ItemCompare(wx.Panel):
|
|||||||
|
|
||||||
for i, attr in enumerate(self.attrs.keys()):
|
for i, attr in enumerate(self.attrs.keys()):
|
||||||
name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr
|
name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr
|
||||||
|
# Add indicator if this column is selected for bang/buck calculation
|
||||||
|
if self.bangBuckColumn == i + 1:
|
||||||
|
name = "► " + name
|
||||||
self.paramList.InsertColumn(i + 1, name)
|
self.paramList.InsertColumn(i + 1, name)
|
||||||
self.paramList.SetColumnWidth(i + 1, 120)
|
self.paramList.SetColumnWidth(i + 1, 120)
|
||||||
|
|
||||||
self.paramList.InsertColumn(len(self.attrs) + 1, _t("Price"))
|
self.paramList.InsertColumn(len(self.attrs) + 1, _t("Price"))
|
||||||
self.paramList.SetColumnWidth(len(self.attrs) + 1, 60)
|
self.paramList.SetColumnWidth(len(self.attrs) + 1, 60)
|
||||||
|
|
||||||
|
# Add Buck/bang column header
|
||||||
|
buckBangHeader = _t("Buck/bang")
|
||||||
|
if self.bangBuckColumnName:
|
||||||
|
buckBangHeader = _t("Buck/bang ({})").format(self.bangBuckColumnName)
|
||||||
|
self.paramList.InsertColumn(len(self.attrs) + 2, buckBangHeader)
|
||||||
|
self.paramList.SetColumnWidth(len(self.attrs) + 2, 80)
|
||||||
|
|
||||||
toHighlight = []
|
toHighlight = []
|
||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
i = self.paramList.InsertItem(self.paramList.GetItemCount(), item.name)
|
i = self.paramList.InsertItem(self.paramList.GetItemCount(), item.name)
|
||||||
for x, attr in enumerate(self.attrs.keys()):
|
for x, attr in enumerate(self.attrs.keys()):
|
||||||
if attr in item.attributes:
|
# Handle computed attributes
|
||||||
|
if attr in self.computedAttrs:
|
||||||
|
computed = self.computedAttrs[attr]
|
||||||
|
amountAttr = computed["amountAttr"]
|
||||||
|
durationAttr = computed["durationAttr"]
|
||||||
|
|
||||||
|
# Item needs both attributes to display per-second value
|
||||||
|
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||||
|
amountValue = item.attributes[amountAttr].value
|
||||||
|
durationValue = item.attributes[durationAttr].value
|
||||||
|
# Duration is in milliseconds, convert to seconds
|
||||||
|
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||||
|
|
||||||
|
info = self.attrs[attr]
|
||||||
|
if self.toggleView == 1:
|
||||||
|
valueUnit = formatAmount(perSecondValue, 3, 0, 0) + " " + info.unit.displayName
|
||||||
|
else:
|
||||||
|
valueUnit = str(perSecondValue)
|
||||||
|
|
||||||
|
self.paramList.SetItem(i, x + 1, valueUnit)
|
||||||
|
# else: leave cell empty
|
||||||
|
elif attr in item.attributes:
|
||||||
info = self.attrs[attr]
|
info = self.attrs[attr]
|
||||||
value = item.attributes[attr].value
|
value = item.attributes[attr].value
|
||||||
if self.toggleView != 1:
|
if self.toggleView != 1:
|
||||||
@@ -191,6 +347,27 @@ class ItemCompare(wx.Panel):
|
|||||||
|
|
||||||
# Add prices
|
# Add prices
|
||||||
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
|
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
|
||||||
|
|
||||||
|
# Add buck/bang values
|
||||||
|
if self.bangBuckColumn is not None and item.price.price and item.price.price > 0:
|
||||||
|
attr_key = list(self.attrs.keys())[self.bangBuckColumn - 1]
|
||||||
|
if attr_key in self.computedAttrs:
|
||||||
|
computed = self.computedAttrs[attr_key]
|
||||||
|
amountAttr = computed["amountAttr"]
|
||||||
|
durationAttr = computed["durationAttr"]
|
||||||
|
if amountAttr in item.attributes and durationAttr in item.attributes:
|
||||||
|
amountValue = item.attributes[amountAttr].value
|
||||||
|
durationValue = item.attributes[durationAttr].value
|
||||||
|
perSecondValue = amountValue / (durationValue / 1000.0) if durationValue > 0 else 0
|
||||||
|
if perSecondValue > 0:
|
||||||
|
buckBangValue = item.price.price / perSecondValue
|
||||||
|
self.paramList.SetItem(i, len(self.attrs) + 2, formatAmount(buckBangValue, 3, 3, 9, currency=True))
|
||||||
|
elif attr_key in item.attributes:
|
||||||
|
attrValue = item.attributes[attr_key].value
|
||||||
|
if attrValue > 0:
|
||||||
|
buckBangValue = item.price.price / attrValue
|
||||||
|
self.paramList.SetItem(i, len(self.attrs) + 2, formatAmount(buckBangValue, 3, 3, 9, currency=True))
|
||||||
|
|
||||||
if item.name in self.highlightedNames:
|
if item.name in self.highlightedNames:
|
||||||
toHighlight.append(i)
|
toHighlight.append(i)
|
||||||
|
|
||||||
|
|||||||
@@ -497,11 +497,27 @@ class FittingView(d.Display):
|
|||||||
fit = Fit.getInstance().getFit(fitID)
|
fit = Fit.getInstance().getFit(fitID)
|
||||||
if mod in fit.modules:
|
if mod in fit.modules:
|
||||||
position = fit.modules.index(mod)
|
position = fit.modules.index(mod)
|
||||||
self.mainFrame.command.Submit(cmd.GuiCargoToLocalModuleCommand(
|
modifiers = wx.GetMouseState().GetModifiers()
|
||||||
|
isCopy = modifiers == wx.MOD_CONTROL
|
||||||
|
isBatch = modifiers == wx.MOD_SHIFT
|
||||||
|
if isBatch:
|
||||||
|
self.mainFrame.command.Submit(
|
||||||
|
cmd.GuiBatchCargoToLocalModuleCommand(
|
||||||
|
fitID=fitID,
|
||||||
|
cargoItemID=cargoItemID,
|
||||||
|
targetPosition=position,
|
||||||
|
copy=isCopy,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.mainFrame.command.Submit(
|
||||||
|
cmd.GuiCargoToLocalModuleCommand(
|
||||||
fitID=fitID,
|
fitID=fitID,
|
||||||
cargoItemID=cargoItemID,
|
cargoItemID=cargoItemID,
|
||||||
modPosition=position,
|
modPosition=position,
|
||||||
copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
|
copy=isCopy,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def swapItems(self, x, y, srcIdx):
|
def swapItems(self, x, y, srcIdx):
|
||||||
"""Swap two modules in fitting window"""
|
"""Swap two modules in fitting window"""
|
||||||
@@ -795,7 +811,6 @@ class FittingView(d.Display):
|
|||||||
del mod.restrictionOverridden
|
del mod.restrictionOverridden
|
||||||
hasRestrictionOverriden = not hasRestrictionOverriden
|
hasRestrictionOverriden = not hasRestrictionOverriden
|
||||||
|
|
||||||
|
|
||||||
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
|
if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
|
||||||
self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor)
|
self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor)
|
||||||
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
|
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ from .gui.localModule.mutatedRevert import GuiRevertMutatedLocalModuleCommand
|
|||||||
from .gui.localModule.remove import GuiRemoveLocalModuleCommand
|
from .gui.localModule.remove import GuiRemoveLocalModuleCommand
|
||||||
from .gui.localModule.replace import GuiReplaceLocalModuleCommand
|
from .gui.localModule.replace import GuiReplaceLocalModuleCommand
|
||||||
from .gui.localModule.swap import GuiSwapLocalModulesCommand
|
from .gui.localModule.swap import GuiSwapLocalModulesCommand
|
||||||
|
from .gui.localModuleCargo.batchCargoToLocalModule import (
|
||||||
|
GuiBatchCargoToLocalModuleCommand,
|
||||||
|
)
|
||||||
|
from .gui.localModuleCargo.batchLocalModuleToCargo import (
|
||||||
|
GuiBatchLocalModuleToCargoCommand,
|
||||||
|
)
|
||||||
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
|
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
|
||||||
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
|
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
|
||||||
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
|
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
|
||||||
|
|||||||
325
gui/fitCommands/gui/localModuleCargo/batchCargoToLocalModule.py
Normal file
325
gui/fitCommands/gui/localModuleCargo/batchCargoToLocalModule.py
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
import wx
|
||||||
|
|
||||||
|
import eos.db
|
||||||
|
import gui.mainFrame
|
||||||
|
from gui import globalEvents as GE
|
||||||
|
from gui.fitCommands.calc.cargo.add import CalcAddCargoCommand
|
||||||
|
from gui.fitCommands.calc.cargo.remove import CalcRemoveCargoCommand
|
||||||
|
from gui.fitCommands.calc.module.changeCharges import CalcChangeModuleChargesCommand
|
||||||
|
from gui.fitCommands.calc.module.localReplace import CalcReplaceLocalModuleCommand
|
||||||
|
from gui.fitCommands.helpers import (
|
||||||
|
CargoInfo,
|
||||||
|
InternalCommandHistory,
|
||||||
|
ModuleInfo,
|
||||||
|
restoreRemovedDummies,
|
||||||
|
)
|
||||||
|
from service.fit import Fit
|
||||||
|
|
||||||
|
|
||||||
|
class GuiBatchCargoToLocalModuleCommand(wx.Command):
|
||||||
|
def __init__(self, fitID, cargoItemID, targetPosition, copy):
|
||||||
|
wx.Command.__init__(self, True, "Batch Cargo to Local Modules")
|
||||||
|
self.internalHistory = InternalCommandHistory()
|
||||||
|
self.fitID = fitID
|
||||||
|
self.srcCargoItemID = cargoItemID
|
||||||
|
self.targetPosition = targetPosition
|
||||||
|
self.copy = copy
|
||||||
|
self.replacedModItemIDs = []
|
||||||
|
self.savedRemovedDummies = None
|
||||||
|
|
||||||
|
def Do(self):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(self.fitID)
|
||||||
|
if fit is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
srcCargo = next((c for c in fit.cargo if c.itemID == self.srcCargoItemID), None)
|
||||||
|
if srcCargo is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if srcCargo.item.isCharge:
|
||||||
|
return self._handleCharges(fit, srcCargo)
|
||||||
|
|
||||||
|
if not srcCargo.item.isModule:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.targetPosition >= len(fit.modules):
|
||||||
|
return False
|
||||||
|
|
||||||
|
targetMod = fit.modules[self.targetPosition]
|
||||||
|
|
||||||
|
if targetMod.isEmpty:
|
||||||
|
return self._fillEmptySlots(fit, srcCargo, targetMod.slot)
|
||||||
|
else:
|
||||||
|
return self._replaceSimilarModules(fit, srcCargo, targetMod)
|
||||||
|
|
||||||
|
def _getSimilarModulePositions(self, fit, targetMod):
|
||||||
|
targetItemID = targetMod.itemID
|
||||||
|
matchingPositions = []
|
||||||
|
for position, mod in enumerate(fit.modules):
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
if mod.itemID == targetItemID:
|
||||||
|
matchingPositions.append(position)
|
||||||
|
return matchingPositions
|
||||||
|
|
||||||
|
def _replaceSimilarModules(self, fit, srcCargo, targetMod):
|
||||||
|
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||||
|
|
||||||
|
matchingPositions = self._getSimilarModulePositions(fit, targetMod)
|
||||||
|
|
||||||
|
if not matchingPositions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
positionsToReplace = matchingPositions[: int(availableAmount)]
|
||||||
|
|
||||||
|
if not positionsToReplace:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.replacedModItemIDs = []
|
||||||
|
commands = []
|
||||||
|
cargoToRemove = 0
|
||||||
|
|
||||||
|
for position in positionsToReplace:
|
||||||
|
mod = fit.modules[position]
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
dstModItemID = mod.itemID
|
||||||
|
|
||||||
|
newModInfo = ModuleInfo.fromModule(mod, unmutate=True)
|
||||||
|
newModInfo.itemID = self.srcCargoItemID
|
||||||
|
newCargoModItemID = ModuleInfo.fromModule(mod, unmutate=True).itemID
|
||||||
|
|
||||||
|
commands.append(
|
||||||
|
CalcAddCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(itemID=newCargoModItemID, amount=1),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cmdReplace = CalcReplaceLocalModuleCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
position=position,
|
||||||
|
newModInfo=newModInfo,
|
||||||
|
unloadInvalidCharges=True,
|
||||||
|
)
|
||||||
|
commands.append(cmdReplace)
|
||||||
|
self.replacedModItemIDs.append(dstModItemID)
|
||||||
|
cargoToRemove += 1
|
||||||
|
|
||||||
|
if not self.copy and cargoToRemove > 0:
|
||||||
|
commands.insert(
|
||||||
|
0,
|
||||||
|
CalcRemoveCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(
|
||||||
|
itemID=self.srcCargoItemID, amount=cargoToRemove
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
success = self.internalHistory.submitBatch(*commands)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
self.internalHistory.undoAll()
|
||||||
|
return False
|
||||||
|
|
||||||
|
eos.db.flush()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
|
||||||
|
events = []
|
||||||
|
for removedModItemID in self.replacedModItemIDs:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="moddel", typeID=removedModItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.srcCargoItemID is not None:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="modadd", typeID=self.srcCargoItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not events:
|
||||||
|
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _fillEmptySlots(self, fit, srcCargo, targetSlot):
|
||||||
|
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||||
|
|
||||||
|
emptyPositions = []
|
||||||
|
for position, mod in enumerate(fit.modules):
|
||||||
|
if mod.isEmpty and mod.slot == targetSlot:
|
||||||
|
emptyPositions.append(position)
|
||||||
|
|
||||||
|
if not emptyPositions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
positionsToFill = emptyPositions[: int(availableAmount)]
|
||||||
|
|
||||||
|
if not positionsToFill:
|
||||||
|
return False
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
cargoToRemove = 0
|
||||||
|
|
||||||
|
for position in positionsToFill:
|
||||||
|
newModInfo = ModuleInfo(itemID=self.srcCargoItemID)
|
||||||
|
cmdReplace = CalcReplaceLocalModuleCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
position=position,
|
||||||
|
newModInfo=newModInfo,
|
||||||
|
unloadInvalidCharges=True,
|
||||||
|
)
|
||||||
|
commands.append(cmdReplace)
|
||||||
|
cargoToRemove += 1
|
||||||
|
|
||||||
|
if not self.copy and cargoToRemove > 0:
|
||||||
|
commands.insert(
|
||||||
|
0,
|
||||||
|
CalcRemoveCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(
|
||||||
|
itemID=self.srcCargoItemID, amount=cargoToRemove
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
success = self.internalHistory.submitBatch(*commands)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
self.internalHistory.undoAll()
|
||||||
|
return False
|
||||||
|
|
||||||
|
eos.db.flush()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
|
||||||
|
events = []
|
||||||
|
if self.srcCargoItemID is not None:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="modadd", typeID=self.srcCargoItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not events:
|
||||||
|
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _handleCharges(self, fit, srcCargo):
|
||||||
|
availableAmount = srcCargo.amount if not self.copy else float("inf")
|
||||||
|
|
||||||
|
targetMod = fit.modules[self.targetPosition]
|
||||||
|
if targetMod.isEmpty:
|
||||||
|
return False
|
||||||
|
|
||||||
|
targetItemID = targetMod.itemID
|
||||||
|
matchingPositions = []
|
||||||
|
for position, mod in enumerate(fit.modules):
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
if mod.itemID == targetItemID:
|
||||||
|
matchingPositions.append(position)
|
||||||
|
|
||||||
|
if not matchingPositions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
positionsToReplace = matchingPositions[: int(availableAmount)]
|
||||||
|
if not positionsToReplace:
|
||||||
|
return False
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
chargeMap = {}
|
||||||
|
totalChargesNeeded = 0
|
||||||
|
|
||||||
|
for position in positionsToReplace:
|
||||||
|
mod = fit.modules[position]
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
oldChargeID = mod.chargeID
|
||||||
|
oldChargeAmount = mod.numCharges
|
||||||
|
newChargeAmount = mod.getNumCharges(srcCargo.item)
|
||||||
|
|
||||||
|
if oldChargeID is not None and oldChargeID != srcCargo.itemID:
|
||||||
|
commands.append(
|
||||||
|
CalcAddCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(itemID=oldChargeID, amount=oldChargeAmount),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
chargeMap[position] = srcCargo.itemID
|
||||||
|
totalChargesNeeded += newChargeAmount
|
||||||
|
|
||||||
|
if not self.copy and totalChargesNeeded > 0:
|
||||||
|
commands.append(
|
||||||
|
CalcRemoveCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(
|
||||||
|
itemID=srcCargo.itemID, amount=totalChargesNeeded
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
commands.append(
|
||||||
|
CalcChangeModuleChargesCommand(
|
||||||
|
fitID=self.fitID, projected=False, chargeMap=chargeMap
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
success = self.internalHistory.submitBatch(*commands)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
self.internalHistory.undoAll()
|
||||||
|
return False
|
||||||
|
|
||||||
|
eos.db.flush()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
|
||||||
|
events = [GE.FitChanged(fitIDs=(self.fitID,))]
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def Undo(self):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(self.fitID)
|
||||||
|
restoreRemovedDummies(fit, self.savedRemovedDummies)
|
||||||
|
success = self.internalHistory.undoAll()
|
||||||
|
eos.db.flush()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
events = []
|
||||||
|
if self.srcCargoItemID is not None:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="moddel", typeID=self.srcCargoItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for removedModItemID in self.replacedModItemIDs:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="modadd", typeID=removedModItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not events:
|
||||||
|
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
return success
|
||||||
167
gui/fitCommands/gui/localModuleCargo/batchLocalModuleToCargo.py
Normal file
167
gui/fitCommands/gui/localModuleCargo/batchLocalModuleToCargo.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import wx
|
||||||
|
|
||||||
|
import eos.db
|
||||||
|
import gui.mainFrame
|
||||||
|
from gui import globalEvents as GE
|
||||||
|
from gui.fitCommands.calc.cargo.add import CalcAddCargoCommand
|
||||||
|
from gui.fitCommands.calc.module.localRemove import CalcRemoveLocalModulesCommand
|
||||||
|
from gui.fitCommands.helpers import (
|
||||||
|
CargoInfo,
|
||||||
|
InternalCommandHistory,
|
||||||
|
ModuleInfo,
|
||||||
|
restoreRemovedDummies,
|
||||||
|
)
|
||||||
|
from service.fit import Fit
|
||||||
|
|
||||||
|
|
||||||
|
class GuiBatchLocalModuleToCargoCommand(wx.Command):
|
||||||
|
def __init__(self, fitID, modPosition, copy):
|
||||||
|
wx.Command.__init__(self, True, "Batch Local Module to Cargo")
|
||||||
|
self.internalHistory = InternalCommandHistory()
|
||||||
|
self.fitID = fitID
|
||||||
|
self.srcModPosition = modPosition
|
||||||
|
self.copy = copy
|
||||||
|
self.removedModItemIDs = []
|
||||||
|
self.savedRemovedDummies = None
|
||||||
|
|
||||||
|
def Do(self):
|
||||||
|
fit = Fit.getInstance().getFit(self.fitID)
|
||||||
|
srcMod = fit.modules[self.srcModPosition]
|
||||||
|
if srcMod.isEmpty:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if srcMod.chargeID is not None:
|
||||||
|
return self._unloadCharges(fit, srcMod)
|
||||||
|
else:
|
||||||
|
return self._moveModulesToCargo(fit, srcMod)
|
||||||
|
|
||||||
|
def _getSimilarModulePositions(self, fit, targetMod):
|
||||||
|
targetItemID = targetMod.itemID
|
||||||
|
matchingPositions = []
|
||||||
|
for position, mod in enumerate(fit.modules):
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
if mod.itemID == targetItemID:
|
||||||
|
matchingPositions.append(position)
|
||||||
|
return matchingPositions
|
||||||
|
|
||||||
|
def _unloadCharges(self, fit, srcMod):
|
||||||
|
matchingPositions = self._getSimilarModulePositions(fit, srcMod)
|
||||||
|
if not matchingPositions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
for position in matchingPositions:
|
||||||
|
mod = fit.modules[position]
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
if mod.chargeID is not None:
|
||||||
|
commands.append(
|
||||||
|
CalcAddCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(itemID=mod.chargeID, amount=mod.numCharges),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.copy:
|
||||||
|
from gui.fitCommands.calc.module.changeCharges import (
|
||||||
|
CalcChangeModuleChargesCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
chargeMap = {pos: None for pos in matchingPositions}
|
||||||
|
commands.append(
|
||||||
|
CalcChangeModuleChargesCommand(
|
||||||
|
fitID=self.fitID, projected=False, chargeMap=chargeMap
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
success = self.internalHistory.submitBatch(*commands)
|
||||||
|
if not success:
|
||||||
|
self.internalHistory.undoAll()
|
||||||
|
return False
|
||||||
|
|
||||||
|
eos.db.flush()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
|
||||||
|
events = [GE.FitChanged(fitIDs=(self.fitID,))]
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
return success
|
||||||
|
|
||||||
|
def _moveModulesToCargo(self, fit, srcMod):
|
||||||
|
matchingPositions = self._getSimilarModulePositions(fit, srcMod)
|
||||||
|
if not matchingPositions:
|
||||||
|
return False
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
for position in matchingPositions:
|
||||||
|
mod = fit.modules[position]
|
||||||
|
if mod.isEmpty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
commands.append(
|
||||||
|
CalcAddCargoCommand(
|
||||||
|
fitID=self.fitID,
|
||||||
|
cargoInfo=CargoInfo(
|
||||||
|
itemID=ModuleInfo.fromModule(mod, unmutate=True).itemID,
|
||||||
|
amount=1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.removedModItemIDs.append(mod.itemID)
|
||||||
|
|
||||||
|
if not self.copy:
|
||||||
|
commands.append(
|
||||||
|
CalcRemoveLocalModulesCommand(
|
||||||
|
fitID=self.fitID, positions=matchingPositions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
success = self.internalHistory.submitBatch(*commands)
|
||||||
|
if not success:
|
||||||
|
self.internalHistory.undoAll()
|
||||||
|
return False
|
||||||
|
|
||||||
|
eos.db.flush()
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
self.savedRemovedDummies = sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
|
||||||
|
events = []
|
||||||
|
for removedModItemID in self.removedModItemIDs:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="moddel", typeID=removedModItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not events:
|
||||||
|
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
return success
|
||||||
|
|
||||||
|
def Undo(self):
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(self.fitID)
|
||||||
|
restoreRemovedDummies(fit, self.savedRemovedDummies)
|
||||||
|
success = self.internalHistory.undoAll()
|
||||||
|
eos.db.flush()
|
||||||
|
sFit.recalc(self.fitID)
|
||||||
|
sFit.fill(self.fitID)
|
||||||
|
eos.db.commit()
|
||||||
|
events = []
|
||||||
|
for removedModItemID in self.removedModItemIDs:
|
||||||
|
events.append(
|
||||||
|
GE.FitChanged(
|
||||||
|
fitIDs=(self.fitID,), action="modadd", typeID=removedModItemID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not events:
|
||||||
|
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
|
||||||
|
for event in events:
|
||||||
|
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
|
||||||
|
return success
|
||||||
@@ -20,15 +20,19 @@
|
|||||||
|
|
||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
import wx
|
import wx
|
||||||
from collections import Counter
|
import re
|
||||||
|
|
||||||
from eos.const import FittingSlot
|
|
||||||
from service.fit import Fit as svcFit
|
from service.fit import Fit as svcFit
|
||||||
from service.port.eft import exportEft, importEft, _importPrepare
|
from service.port.eft import exportEft
|
||||||
from service.const import PortEftOptions
|
from service.const import PortEftOptions
|
||||||
|
|
||||||
_t = wx.GetTranslation
|
_t = wx.GetTranslation
|
||||||
|
|
||||||
|
# Regex for parsing items: itemName x? quantity?, ,? chargeName?
|
||||||
|
ITEM_REGEX = re.compile(
|
||||||
|
r"^(?P<itemName>[-\'\w\s]+?)x?\s*(?P<quantity>\d+)?\s*(?:,\s*(?P<chargeName>[-\'\w\s]+))?$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FitDiffFrame(wx.Frame):
|
class FitDiffFrame(wx.Frame):
|
||||||
"""A frame to display differences between two fits."""
|
"""A frame to display differences between two fits."""
|
||||||
@@ -169,158 +173,44 @@ class FitDiffFrame(wx.Frame):
|
|||||||
self.diffText.SetValue('\n'.join(diffLines))
|
self.diffText.SetValue('\n'.join(diffLines))
|
||||||
|
|
||||||
def parsePastedFit(self, text):
|
def parsePastedFit(self, text):
|
||||||
"""Parse pasted EFT text into a fit object."""
|
"""Parse pasted EFT text into a map of item name to count."""
|
||||||
try:
|
items = {}
|
||||||
lines = _importPrepare(text.splitlines())
|
for line in text.splitlines():
|
||||||
if not lines:
|
line = line.strip()
|
||||||
return None
|
if not line or line.startswith("["):
|
||||||
return importEft(lines)
|
continue
|
||||||
except Exception:
|
match = ITEM_REGEX.match(line)
|
||||||
return None
|
if match:
|
||||||
|
item_name = match.group("itemName").strip()
|
||||||
|
quantity = match.group("quantity")
|
||||||
|
count = int(quantity) if quantity else 1
|
||||||
|
if item_name not in items:
|
||||||
|
items[item_name] = 0
|
||||||
|
items[item_name] += count
|
||||||
|
return items
|
||||||
|
|
||||||
def calculateDiff(self, fit1, fit2):
|
def calculateDiff(self, fit1_items, fit2_items):
|
||||||
"""Calculate items needed to transform fit1 into fit2.
|
"""Calculate items needed to transform fit1 into fit2.
|
||||||
|
|
||||||
Returns a list of strings in the format: "<item> <quantity>"
|
Returns a list of strings showing additions and extra items.
|
||||||
Only shows items that need to be added (no negative values).
|
|
||||||
"""
|
"""
|
||||||
diffLines = []
|
diffLines = []
|
||||||
|
|
||||||
# Get module counts by type for each fit (grouped by slot type)
|
all_items = set(fit1_items.keys()) | set(fit2_items.keys())
|
||||||
fit1_modules = self.getModuleCounts(fit1)
|
additions = []
|
||||||
fit2_modules = self.getModuleCounts(fit2)
|
extras = []
|
||||||
|
|
||||||
# Slot order
|
for item in sorted(all_items):
|
||||||
slotOrder = [
|
count1 = fit1_items.get(item, 0)
|
||||||
FittingSlot.HIGH,
|
count2 = fit2_items.get(item, 0)
|
||||||
FittingSlot.MED,
|
|
||||||
FittingSlot.LOW,
|
|
||||||
FittingSlot.RIG,
|
|
||||||
FittingSlot.SUBSYSTEM,
|
|
||||||
FittingSlot.SERVICE,
|
|
||||||
]
|
|
||||||
|
|
||||||
# Diff modules by slot - only show items needed to add
|
|
||||||
for slot in slotOrder:
|
|
||||||
fit1_slot_modules = fit1_modules.get(slot, Counter())
|
|
||||||
fit2_slot_modules = fit2_modules.get(slot, Counter())
|
|
||||||
|
|
||||||
all_module_types = set(fit1_slot_modules.keys()) | set(fit2_slot_modules.keys())
|
|
||||||
slot_diff_lines = []
|
|
||||||
for module_type in sorted(all_module_types):
|
|
||||||
count1 = fit1_slot_modules.get(module_type, 0)
|
|
||||||
count2 = fit2_slot_modules.get(module_type, 0)
|
|
||||||
if count2 > count1:
|
if count2 > count1:
|
||||||
slot_diff_lines.append(f"{module_type} x{count2 - count1}")
|
additions.append(f"{item} x{count2 - count1}")
|
||||||
|
elif count1 > count2:
|
||||||
|
extras.append(f"{item} x-{count1 - count2}")
|
||||||
|
|
||||||
if slot_diff_lines:
|
diffLines.extend(additions)
|
||||||
if diffLines:
|
if additions and extras:
|
||||||
diffLines.append("")
|
diffLines.extend(["", ""])
|
||||||
diffLines.extend(slot_diff_lines)
|
diffLines.extend(extras)
|
||||||
|
|
||||||
# Get drone counts
|
|
||||||
fit1_drones = self.getDroneCounts(fit1)
|
|
||||||
fit2_drones = self.getDroneCounts(fit2)
|
|
||||||
|
|
||||||
all_drone_types = set(fit1_drones.keys()) | set(fit2_drones.keys())
|
|
||||||
for drone_type in sorted(all_drone_types):
|
|
||||||
count1 = fit1_drones.get(drone_type, 0)
|
|
||||||
count2 = fit2_drones.get(drone_type, 0)
|
|
||||||
if count2 > count1:
|
|
||||||
diffLines.append(f"{drone_type} x{count2 - count1}")
|
|
||||||
|
|
||||||
# Get fighter counts
|
|
||||||
fit1_fighters = self.getFighterCounts(fit1)
|
|
||||||
fit2_fighters = self.getFighterCounts(fit2)
|
|
||||||
|
|
||||||
all_fighter_types = set(fit1_fighters.keys()) | set(fit2_fighters.keys())
|
|
||||||
for fighter_type in sorted(all_fighter_types):
|
|
||||||
count1 = fit1_fighters.get(fighter_type, 0)
|
|
||||||
count2 = fit2_fighters.get(fighter_type, 0)
|
|
||||||
if count2 > count1:
|
|
||||||
diffLines.append(f"{fighter_type} x{count2 - count1}")
|
|
||||||
|
|
||||||
# Get cargo counts
|
|
||||||
fit1_cargo = self.getCargoCounts(fit1)
|
|
||||||
fit2_cargo = self.getCargoCounts(fit2)
|
|
||||||
|
|
||||||
all_cargo_types = set(fit1_cargo.keys()) | set(fit2_cargo.keys())
|
|
||||||
for cargo_type in sorted(all_cargo_types):
|
|
||||||
count1 = fit1_cargo.get(cargo_type, 0)
|
|
||||||
count2 = fit2_cargo.get(cargo_type, 0)
|
|
||||||
if count2 > count1:
|
|
||||||
diffLines.append(f"{cargo_type} x{count2 - count1}")
|
|
||||||
|
|
||||||
# Get implants
|
|
||||||
fit1_implants = self.getImplantNames(fit1)
|
|
||||||
fit2_implants = self.getImplantNames(fit2)
|
|
||||||
|
|
||||||
for implant in sorted(fit2_implants - fit1_implants):
|
|
||||||
diffLines.append(f"{implant} x1")
|
|
||||||
|
|
||||||
# Get boosters
|
|
||||||
fit1_boosters = self.getBoosterNames(fit1)
|
|
||||||
fit2_boosters = self.getBoosterNames(fit2)
|
|
||||||
|
|
||||||
for booster in sorted(fit2_boosters - fit1_boosters):
|
|
||||||
diffLines.append(f"{booster} x1")
|
|
||||||
|
|
||||||
return diffLines
|
return diffLines
|
||||||
|
|
||||||
def getModuleCounts(self, fit):
|
|
||||||
"""Get a counter of module types for a fit, grouped by slot type.
|
|
||||||
|
|
||||||
Returns a dict mapping FittingSlot -> Counter of module names.
|
|
||||||
Position doesn't matter, just counts by module name.
|
|
||||||
"""
|
|
||||||
counts_by_slot = {}
|
|
||||||
for module in fit.modules:
|
|
||||||
if module.isEmpty:
|
|
||||||
continue
|
|
||||||
slot = module.slot
|
|
||||||
if slot not in counts_by_slot:
|
|
||||||
counts_by_slot[slot] = Counter()
|
|
||||||
# Use item type name for comparison
|
|
||||||
name = module.item.typeName if module.item else ""
|
|
||||||
counts_by_slot[slot][name] += 1
|
|
||||||
return counts_by_slot
|
|
||||||
|
|
||||||
def getDroneCounts(self, fit):
|
|
||||||
"""Get a counter of drone types for a fit."""
|
|
||||||
counts = Counter()
|
|
||||||
for drone in fit.drones:
|
|
||||||
if drone.item:
|
|
||||||
counts[drone.item.typeName] += drone.amount
|
|
||||||
return counts
|
|
||||||
|
|
||||||
def getFighterCounts(self, fit):
|
|
||||||
"""Get a counter of fighter types for a fit."""
|
|
||||||
counts = Counter()
|
|
||||||
for fighter in fit.fighters:
|
|
||||||
if fighter.item:
|
|
||||||
counts[fighter.item.typeName] += fighter.amount
|
|
||||||
return counts
|
|
||||||
|
|
||||||
def getCargoCounts(self, fit):
|
|
||||||
"""Get a counter of cargo items for a fit."""
|
|
||||||
counts = Counter()
|
|
||||||
for cargo in fit.cargo:
|
|
||||||
if cargo.item:
|
|
||||||
counts[cargo.item.typeName] += cargo.amount
|
|
||||||
return counts
|
|
||||||
|
|
||||||
def getImplantNames(self, fit):
|
|
||||||
"""Get a set of implant names for a fit."""
|
|
||||||
names = set()
|
|
||||||
for implant in fit.implants:
|
|
||||||
if implant.item:
|
|
||||||
names.add(implant.item.typeName)
|
|
||||||
return names
|
|
||||||
|
|
||||||
def getBoosterNames(self, fit):
|
|
||||||
"""Get a set of booster names for a fit."""
|
|
||||||
names = set()
|
|
||||||
for booster in fit.boosters:
|
|
||||||
if booster.item:
|
|
||||||
names.add(booster.item.typeName)
|
|
||||||
return names
|
|
||||||
|
|||||||
Reference in New Issue
Block a user