NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

hexMapUtil.py (37573B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\mapView\hexagonal\hexMapUtil.py
      2 from __future__ import print_function
      3 import geo2
      4 import blue
      5 from carbon.common.script.util.timerstuff import AutoTimer
      6 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText
      7 from carbonui.primitives.container import Container
      8 from carbonui.primitives.fill import Fill
      9 from carbonui.primitives.frame import Frame
     10 from carbonui.primitives.layoutGrid import LayoutGrid
     11 from carbonui.primitives.line import Line
     12 from carbonui.control.button import Button
     13 from carbonui.control.checkbox import Checkbox
     14 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelLarge
     15 from eve.common.script.sys.idCheckers import IsRegion
     16 import uthread
     17 import sys
     18 import carbonui.const as uiconst
     19 from carbonui.control.window import Window
     20 from eve.client.script.ui.shared.mapView.hexagonal import hexUtil
     21 from carbonui.uicore import uicore
     22 from eve.common.lib import appConst as const
     23 FIXED_LAYOUT_BY_ID = {9: {10000001: (-4, 1),
     24      10000002: (0, -2),
     25      10000003: (-1, -4),
     26      10000004: (9, -1),
     27      10000005: (-9, 0),
     28      10000006: (-7, -1),
     29      10000007: (-7, -4),
     30      10000008: (-6, -1),
     31      10000009: (-8, -2),
     32      10000010: (-2, -5),
     33      10000011: (-5, -2),
     34      10000012: (-6, 1),
     35      10000013: (-3, -4),
     36      10000014: (-2, 3),
     37      10000015: (-2, -6),
     38      10000016: (0, -5),
     39      10000017: (9, 1),
     40      10000018: (-5, -5),
     41      10000019: (10, 0),
     42      10000020: (6, 4),
     43      10000021: (-6, -5),
     44      10000022: (-2, 5),
     45      10000023: (1, -7),
     46      10000025: (-5, 7),
     47      10000027: (-4, -3),
     48      10000028: (-2, 0),
     49      10000029: (-2, -2),
     50      10000030: (0, 0),
     51      10000031: (-5, 3),
     52      10000032: (4, -1),
     53      10000033: (3, -4),
     54      10000034: (-2, -3),
     55      10000035: (0, -7),
     56      10000036: (0, 1),
     57      10000037: (8, -2),
     58      10000038: (2, 0),
     59      10000039: (-4, 4),
     60      10000040: (-4, -6),
     61      10000041: (4, -7),
     62      10000042: (0, -1),
     63      10000043: (3, 1),
     64      10000044: (7, -6),
     65      10000045: (-2, -7),
     66      10000046: (1, -8),
     67      10000047: (-1, 1),
     68      10000048: (4, -5),
     69      10000049: (4, 7),
     70      10000050: (1, 6),
     71      10000051: (3, -8),
     72      10000052: (7, -1),
     73      10000053: (-5, -8),
     74      10000054: (5, 1),
     75      10000055: (0, -8),
     76      10000056: (-5, 4),
     77      10000057: (1, -4),
     78      10000058: (2, -2),
     79      10000059: (-3, 6),
     80      10000060: (0, 3),
     81      10000061: (-7, 2),
     82      10000062: (-5, 5),
     83      10000063: (-1, 5),
     84      10000064: (8, -3),
     85      10000065: (8, 3),
     86      10000066: (-3, -6),
     87      10000067: (5, -3),
     88      10000068: (6, -5),
     89      10000069: (1, -6)},
     90  10000021: {20000258: (2, 1),
     91             20000259: (1, 0),
     92             20000260: (0, 1),
     93             20000261: (-1, 1),
     94             20000262: (-1, 0),
     95             20000263: (1, -1),
     96             20000264: (2, -1),
     97             20000265: (0, -1),
     98             20000266: (1, 1),
     99             20000267: (1, -2),
    100             20000268: (-1, -2),
    101             20000269: (-1, -1),
    102             20000270: (-2, -1)}}
    103 SMALLID = -1
    104 MEDIUMID = -2
    105 LARGEID = -3
    106 SLOT_PATTERN_SMALL = [(-3, -2),
    107  (-3, 0),
    108  (-2, 2),
    109  (0, -2),
    110  (0, 0),
    111  (2, 2),
    112  (3, -2),
    113  (3, 0)]
    114 SLOT_PATTERN_MEDIUM = [(-5, -2),
    115  (-4, 2),
    116  (-3, -4),
    117  (-3, -1),
    118  (-2, 2),
    119  (-2, 4),
    120  (-1, -3),
    121  (0, -1),
    122  (0, 1),
    123  (0, 3),
    124  (1, -3),
    125  (2, 2),
    126  (2, 4),
    127  (3, -4),
    128  (3, -1),
    129  (4, 2),
    130  (5, -2)]
    131 SLOT_PATTERN_LARGE = [(-6, -2),
    132  (-6, 0),
    133  (-5, -5),
    134  (-5, 2),
    135  (-4, 5),
    136  (-3, -7),
    137  (-3, -1),
    138  (-2, -5),
    139  (-2, 2),
    140  (-1, -8),
    141  (-1, 5),
    142  (0, -3),
    143  (0, 3),
    144  (1, -6),
    145  (1, -1),
    146  (2, -2),
    147  (2, 5),
    148  (3, -8),
    149  (3, 0),
    150  (5, -7),
    151  (5, -5),
    152  (5, -3),
    153  (5, 2),
    154  (5, 4),
    155  (6, 0),
    156  (8, -4),
    157  (8, -1),
    158  (8, 2),
    159  (8, 4)]
    160 TESTLINEPLOT = -4
    161 TESTLINEPLOT_PATTERN = [(-1, 0),
    162  (0, -1),
    163  (0, 0),
    164  (1, -1),
    165  (1, 0)]
    166 
    167 class DebugMapBrowser(Window):
    168     default_windowID = 'DebugMapBrowser'
    169     default_caption = 'Map Debug View'
    170     autoLoadTimer = None
    171     loadedObjectID = None
    172     currentMap = None
    173     clipRect = None
    174 
    175     def ApplyAttributes(self, attributes):
    176         Window.ApplyAttributes(self, attributes)
    177         uthread.new(self.LoadOptions)
    178 
    179     def LoadOptions(self):
    180         PrimeMapData()
    181         self.objectIDs = sorted(uicore.mapObjectDataByID.keys())
    182         main = self.GetMainArea()
    183         main.clipChildren = True
    184         main.padding = 6
    185         lg = LayoutGrid(parent=main, columns=3, cellPadding=2, align=uiconst.TOTOP)
    186         self.objectLabel = EveLabelLarge(bold=True)
    187         lg.AddCell(self.objectLabel, colSpan=lg.columns - 1)
    188         self.inputEdit = SingleLineEditText(OnReturn=self.OnInputConfirm, align=uiconst.TOPRIGHT)
    189         self.inputEdit.SetHistoryVisibility(True)
    190         lg.AddCell(self.inputEdit, colSpan=1)
    191         b = Button(func=self.BrowsePrev, label='Previous')
    192         lg.AddCell(b, colSpan=2)
    193         Button(parent=lg, func=self.BrowseNext, label='Next', align=uiconst.TOPRIGHT)
    194         lg.FillRow()
    195         lg.AddCell(Line(align=uiconst.TOTOP), colSpan=lg.columns, cellPadding=(0, 4, 0, 4))
    196         b = Button(func=self.BrowseNextLoop, label='Browse next loop', fixedwidth=240)
    197         lg.AddCell(b, colSpan=lg.columns)
    198         self.browseLoopButton = b
    199         settings.user.ui.Set('mapDebugViewRefreshLayout', 0)
    200         c = Checkbox(settingsKey='mapDebugViewRefreshLayout', text='Refresh layouts', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugViewRefreshLayout', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    201         lg.AddCell(c, colSpan=lg.columns)
    202         c = Checkbox(settingsKey='mapDebugViewStopOnErrors', text='Stop on error', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugViewStopOnErrors', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    203         lg.AddCell(c, colSpan=lg.columns)
    204         c = Checkbox(settingsKey='mapDebugViewIgnoreFixed', text='Ignore fixed layouts', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugViewIgnoreFixed', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    205         lg.AddCell(c, colSpan=lg.columns)
    206         c = Checkbox(settingsKey='mapDebugViewEditEnabled', text='Editmode enabled', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugViewEditEnabled', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    207         lg.AddCell(c, colSpan=lg.columns)
    208         c = Checkbox(settingsKey='mapDebugSubdivision', text='Show Grid Subdivision', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugSubdivision', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    209         lg.AddCell(c, colSpan=lg.columns)
    210         c = Checkbox(settingsKey='mapLoadCombined', text='Load Combined', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapLoadCombined', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    211         lg.AddCell(c, colSpan=lg.columns)
    212         c = Checkbox(settingsKey='mapDebugShowIDs', text='Show IDs', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugShowIDs', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    213         lg.AddCell(c, colSpan=lg.columns)
    214         c = Checkbox(settingsKey='mapDebugLoadHexLines', text='Load Hex Lines', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugLoadHexLines', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    215         lg.AddCell(c, colSpan=lg.columns)
    216         c = Checkbox(settingsKey='mapDebugShowExitPoints', text='Show Exit Points', align=uiconst.TOPLEFT, checked=settings.user.ui.Get('mapDebugShowExitPoints', 0), wrapLabel=False, callback=self.OnCheckBoxChange)
    217         lg.AddCell(c, colSpan=lg.columns)
    218         lg.FillRow()
    219         lg.AddCell(Line(align=uiconst.TOTOP), colSpan=lg.columns, cellPadding=(0, 4, 0, 4))
    220         b = Button(func=self.ScanCurrent, label='Scan Current', fixedwidth=240)
    221         lg.AddCell(b, colSpan=lg.columns)
    222         b = Button(func=self.AutoFitSize, label='Auto Fit', fixedwidth=240)
    223         lg.AddCell(b, colSpan=lg.columns)
    224         self.statsLabel = EveLabelMedium(parent=lg)
    225         lg.FillRow()
    226         self.clipRect = Container(parent=uicore.layer.main, align=uiconst.CENTER, width=800, height=600, clipChildren=True)
    227         Frame(bgParent=self.clipRect)
    228         Fill(bgParent=self.clipRect, color=(0, 0, 0, 1))
    229         self.LoadObjectID(loadObjectID=10000001)
    230 
    231     def Close(self, *args, **kwds):
    232         Window.Close(self, *args, **kwds)
    233         if self.currentMap and not self.currentMap.destroyed:
    234             self.currentMap.Close()
    235             self.currentMap = None
    236         if self.clipRect:
    237             self.clipRect.Close()
    238             self.clipRect = None
    239 
    240     def OnCheckBoxChange(self, *args, **kwds):
    241         if self.loadedObjectID:
    242             uthread.new(self.LoadObjectID, self.loadedObjectID)
    243 
    244     def LoadObjectID(self, loadObjectID, *args, **kwds):
    245         self.loadedObjectID = loadObjectID
    246         self.objectLabel.text = '%s %s/%s' % (loadObjectID, self.objectIDs.index(loadObjectID) + 1, len(self.objectIDs))
    247         if getattr(self, 'currentMap', None):
    248             self.currentMap.Close()
    249         inEditMode = settings.user.ui.Get('mapDebugViewEditEnabled', 0)
    250         from eve.client.script.ui.shared.mapView.hexagonal.hexMap import HexMap
    251         self.currentMap = HexMap(parent=self.clipRect, state=uiconst.UI_NORMAL, editMode=inEditMode)
    252         stopOnErrors = settings.user.ui.Get('mapDebugViewStopOnErrors', 0)
    253         ignoreFixed = settings.user.ui.Get('mapDebugViewIgnoreFixed', 0)
    254         refreshLayout = settings.user.ui.Get('mapDebugViewRefreshLayout', 0)
    255         if refreshLayout:
    256             refreshLayout = loadObjectID != const.locationUniverse
    257         if refreshLayout:
    258             resolved = PlotMapObjects(self.currentMap, loadObjectID)
    259             if not resolved and stopOnErrors:
    260                 self.autoLoadTimer = None
    261         else:
    262             self.currentMap.LoadMapData(loadObjectID, ignoreFixedLayout=ignoreFixed)
    263             if loadObjectID > 9 and not settings.user.ui.Get('mapLoadCombined', 0):
    264                 ol, ll, penalty = ScanAllOverlap(self.currentMap)
    265                 if penalty:
    266                     print('Loaded', loadObjectID, 'penalty', penalty)
    267                     if stopOnErrors:
    268                         self.autoLoadTimer = None
    269 
    270     def BrowsePrev(self, *args, **kwds):
    271         self.autoLoadTimer = None
    272         if self.loadedObjectID:
    273             ctrl = uicore.uilib.Key(uiconst.VK_CONTROL)
    274             if ctrl:
    275                 i = 1
    276             else:
    277                 i = self.objectIDs.index(self.loadedObjectID)
    278             self.LoadObjectID(self.objectIDs[max(0, i - 1)])
    279 
    280     def BrowseNext(self, *args, **kwds):
    281         if self.destroyed:
    282             self.autoLoadTimer = None
    283             return
    284         if self.loadedObjectID:
    285             i = self.objectIDs.index(self.loadedObjectID)
    286             if i + 1 < len(self.objectIDs):
    287                 self.LoadObjectID(self.objectIDs[i + 1])
    288                 return True
    289 
    290     def AutoFitSize(self, *args):
    291         self._AutoFitSize(self, *args)
    292 
    293     def _AutoFitSize(self, *args):
    294         hexMap = self.currentMap
    295         layout = hexMap.GetCurrentLayout()
    296         newlayout, size = AutoFitLayout(layout)
    297         for objectID, column_row in newlayout.iteritems():
    298             hexCell = hexMap.objectByID[objectID]
    299             hexCell.MoveToCR(*column_row)
    300 
    301         hexMap.UpdateJumpLines()
    302         hexMap.SetMapSize(size)
    303 
    304     def ScanCurrent(self, *args):
    305         self._ScanCurrent(*args)
    306 
    307     def _ScanCurrent(self, *args):
    308         if self.currentMap:
    309             ol, ll, penalty = ScanAllOverlap(self.currentMap)
    310             print(penalty)
    311 
    312     def MakePattern(self, *args):
    313         pass
    314 
    315     def BrowseNextLoop(self, *args):
    316         if self.autoLoadTimer:
    317             self.browseLoopButton.SetLabel('Browse next loop')
    318             self.autoLoadTimer = None
    319         else:
    320             self.browseLoopButton.SetLabel('Stop')
    321             self.autoLoadTimer = AutoTimer(1, self.AutoBrowse)
    322 
    323     def AutoBrowse(self):
    324         stillLoading = self.BrowseNext()
    325         if not stillLoading:
    326             self.autoLoadTimer = None
    327 
    328     def OnInputConfirm(self, *args, **kwds):
    329         try:
    330             toInt = int(self.inputEdit.GetValue())
    331             if toInt in self.objectIDs:
    332                 self.LoadObjectID(toInt)
    333         except:
    334             pass
    335 
    336 
    337 def PrimeMapData():
    338     starmapService = sm.GetService('starmap')
    339     allRegions = starmapService.GetKnownUniverseRegions()
    340     allConstellations = starmapService.GetKnownUniverseConstellations()
    341     allSolarSystems = starmapService.GetKnownUniverseSolarSystems()
    342     data = {}
    343     jumpsByID = {}
    344     parentByID = {}
    345     exitConnectionsByID = {}
    346     regionJumps = []
    347     jumpsByID[const.locationUniverse] = regionJumps
    348     data[const.locationUniverse] = []
    349     for regionID, regionData in allRegions.iteritems():
    350         if regionID > const.mapWormholeRegionMin:
    351             continue
    352         data[const.locationUniverse].append(regionID)
    353         parentByID[regionID] = const.locationUniverse
    354         for neighbourRegionID in regionData.neighbours:
    355             if neighbourRegionID > const.mapWormholeRegionMin:
    356                 continue
    357             jumpID = sorted((regionID, neighbourRegionID))
    358             if jumpID not in regionJumps:
    359                 regionJumps.append(jumpID)
    360 
    361         jumps = []
    362         jumpsByID[regionID] = jumps
    363         data[regionID] = []
    364         exitJumps = []
    365         exitConnectionsByID[regionID] = exitJumps
    366         for constellationID in regionData.constellationIDs:
    367             if constellationID > const.mapWormholeConstellationMin:
    368                 continue
    369             data[regionID].append(constellationID)
    370             parentByID[constellationID] = regionID
    371             constellation = allConstellations[constellationID]
    372             for neighbourID in constellation.neighbours:
    373                 if neighbourID > const.mapWormholeConstellationMin:
    374                     continue
    375                 neighbourRegionID = allConstellations[neighbourID].regionID
    376                 if neighbourRegionID == constellation.regionID:
    377                     jumpID = sorted((constellationID, neighbourID))
    378                     if jumpID not in jumps:
    379                         jumps.append(jumpID)
    380                 else:
    381                     jumpID = sorted((constellationID, neighbourRegionID))
    382                     if jumpID not in jumps:
    383                         jumps.append(jumpID)
    384 
    385             solarSystemJumps = []
    386             jumpsByID[constellationID] = solarSystemJumps
    387             data[constellationID] = []
    388             for solarSystemID in constellation.solarSystemIDs:
    389                 if solarSystemID > const.mapWormholeSystemMin:
    390                     continue
    391                 data[constellationID].append(solarSystemID)
    392                 parentByID[solarSystemID] = constellationID
    393                 solarSystemData = allSolarSystems[solarSystemID]
    394                 for solarSystemNeighbourID in solarSystemData.neighbours:
    395                     if solarSystemNeighbourID > const.mapWormholeSystemMin:
    396                         continue
    397                     solarSystemNeighbourConstellationID = allSolarSystems[solarSystemNeighbourID].constellationID
    398                     solarSystemNeighbourRegionID = allSolarSystems[solarSystemNeighbourID].regionID
    399                     if solarSystemNeighbourConstellationID == solarSystemData.constellationID:
    400                         jumpID = sorted((solarSystemID, solarSystemNeighbourID))
    401                         if jumpID not in solarSystemJumps:
    402                             solarSystemJumps.append(jumpID)
    403                     else:
    404                         exitConnectionsByID[regionID].append((solarSystemID,
    405                          solarSystemData.constellationID,
    406                          solarSystemNeighbourID,
    407                          solarSystemNeighbourConstellationID))
    408                         if solarSystemNeighbourRegionID == solarSystemData.regionID:
    409                             jumpID = sorted((solarSystemID, solarSystemNeighbourConstellationID))
    410                             if jumpID not in solarSystemJumps:
    411                                 solarSystemJumps.append(jumpID)
    412                         else:
    413                             jumpID = sorted((solarSystemID, solarSystemNeighbourRegionID))
    414                             if jumpID not in solarSystemJumps:
    415                                 solarSystemJumps.append(jumpID)
    416 
    417     for pID, pattern in ((SMALLID, SLOT_PATTERN_SMALL),
    418      (MEDIUMID, SLOT_PATTERN_MEDIUM),
    419      (LARGEID, SLOT_PATTERN_LARGE),
    420      (TESTLINEPLOT, TESTLINEPLOT_PATTERN)):
    421         objs = {}
    422         conn = set()
    423         for i, pos in enumerate(pattern):
    424             objs[i] = pos
    425             for i2, pos2 in enumerate(pattern):
    426                 if i != i2 and (i2, i) not in conn:
    427                     conn.add((i, i2))
    428 
    429         registeredPlot = settings.user.ui.Get('mapHexPlotData', {})
    430         registeredPlot[pID] = objs
    431         settings.user.ui.Set('mapHexPlotData', registeredPlot)
    432         data[pID] = objs
    433         jumpsByID[pID] = list(conn)
    434 
    435     uicore.mapObjectDataByID = data
    436     uicore.mapConnectionDataByID = jumpsByID
    437     uicore.mapObjectParentByID = parentByID
    438     uicore.mapExitConnectionsByID = exitConnectionsByID
    439 
    440 
    441 def FindLeafs(hexMap, level = 0, leafList = None, connectionByObjectID = None):
    442     leafs = leafList or {}
    443     if connectionByObjectID is None:
    444         connectionByObjectID = {}
    445         for fromID, toID in hexMap.connectionsByID.iterkeys():
    446             if uicore.mapObjectParentByID[fromID] != hexMap.objectID:
    447                 continue
    448             if uicore.mapObjectParentByID[toID] != hexMap.objectID:
    449                 continue
    450             connectionByObjectID.setdefault(fromID, []).append(toID)
    451             connectionByObjectID.setdefault(toID, []).append(fromID)
    452 
    453     didFind = False
    454     for objectID in connectionByObjectID.keys():
    455         connections = connectionByObjectID[objectID]
    456         totalConnections = len(connections)
    457         if totalConnections == 1:
    458             connectionByObjectID[connections[0]].remove(objectID)
    459             leafs[objectID] = (connections[0], level)
    460             didFind = True
    461             connectionByObjectID.pop(objectID)
    462 
    463     if didFind:
    464         return FindLeafs(hexMap, level + 1, leafList=leafs, connectionByObjectID=connectionByObjectID)
    465     return (leafs, connectionByObjectID)
    466 
    467 
    468 def ReattachLeafs(hexMap, leafs):
    469     pass
    470 
    471 
    472 def TryRetractLeafs(hexMap, leafs, startPenalty = None, ignoreNoPenalty = False):
    473     ol, ll, bestPenalty = ScanAllOverlap(hexMap)
    474     byLevels = sorted([ (level, leafID, toObjectID) for leafID, (toObjectID, level) in leafs.iteritems() ], reverse=True)
    475     startRange = 0 if IsRegion(hexMap.objectID) else 1
    476     for level, leafID, toObjectID in byLevels:
    477         toObject = hexMap.objectByID.get(toObjectID, None)
    478         leafObject = hexMap.objectByID.get(leafID, None)
    479         if toObject and leafObject:
    480             occupied = hexMap.GetOccupiedSlots()
    481             initPos = leafObject.GetGridPosition()
    482             trySlots = toObject.GetNeighborsInRange(startRange=startRange, endRange=startRange + 1)
    483             bestPos = None
    484             for column_row in trySlots:
    485                 if column_row in occupied:
    486                     continue
    487                 leafObject.MoveToCR(*column_row)
    488                 hexMap.UpdateJumpLines()
    489                 ol, ll, penalty = ScanAllOverlap(hexMap)
    490                 if penalty <= bestPenalty:
    491                     bestPos = column_row
    492                     bestPenalty = penalty
    493                 if penalty == 0 and not ignoreNoPenalty:
    494                     return bestPenalty
    495 
    496             if bestPos:
    497                 leafObject.MoveToCR(*bestPos)
    498             else:
    499                 leafObject.MoveToCR(*initPos)
    500             hexMap.UpdateJumpLines()
    501 
    502     return bestPenalty
    503 
    504 
    505 def PlotMapObjects(hexMap, objectID):
    506     if objectID == 9:
    507         return
    508     plotObjectID = objectID
    509     import random
    510     r = random.Random()
    511     r.seed(objectID)
    512     objectData = uicore.mapObjectDataByID[objectID]
    513     totalObjects = len(objectData)
    514     gap = 1 if IsRegion(objectID) else 2
    515     allSlots = [(0, 0)] + hexUtil.neighbours_amount_step((0, 0), totalObjects, gap, hexMap.isFlatTop, exact=True)
    516     minColumn = 100000000
    517     maxColumn = -100000000
    518     minRow = 100000000
    519     maxRow = -100000000
    520     for col, row in allSlots:
    521         minColumn = min(col, minColumn)
    522         maxColumn = max(col, maxColumn)
    523         minRow = min(row, minRow)
    524         maxRow = max(row, maxRow)
    525 
    526     radius = max(abs(minColumn), maxColumn, abs(minRow), maxRow)
    527     useExitPoint = settings.user.ui.Get('mapDebugShowExitPoints', 0)
    528     if useExitPoint:
    529         radius += 2
    530     size = radius * 2
    531     hexMap.SetMapSize(size)
    532     hexMap.LoadMapData(objectID, loadLayout=False)
    533     objectIDs = sorted(hexMap.objectByID.keys())
    534     print('--------------------------------')
    535     print('Loaded', objectID, len(objectData), len(allSlots), allSlots)
    536     bestLayout = None
    537     bestLayoutPenalty = sys.maxint
    538     hexMap.relocatedHexCells = []
    539     leafs, core = FindLeafs(hexMap)
    540     for s in xrange(1000):
    541         r.shuffle(allSlots)
    542         for i, objectID in enumerate(objectIDs):
    543             hexCell = hexMap.objectByID[objectID]
    544             x, y = allSlots[i]
    545             hexCell.MoveToCR(x, y)
    546 
    547         hexMap.UpdateJumpLines(refreshLayout=True)
    548         ol, penalty = ScanForObjectLineOverlaps(hexMap, stopAtPenalty=bestLayoutPenalty)
    549         if penalty >= bestLayoutPenalty:
    550             blue.synchro.Yield()
    551             continue
    552         ll, penalty = ScanForLineLineOverlaps(hexMap, startPenalty=penalty, stopAtPenalty=bestLayoutPenalty)
    553         if penalty >= bestLayoutPenalty:
    554             blue.synchro.Yield()
    555             continue
    556         blue.synchro.Yield()
    557         if 0 < penalty < 10:
    558             penalty = FixLayout(hexMap, bestLayoutPenalty, leafs, totalObjects)
    559         bestLayoutPenalty = penalty
    560         bestLayout = hexMap.GetCurrentLayout()
    561         print('  New penalty', penalty, s)
    562         if bestLayoutPenalty == 0:
    563             break
    564         blue.synchro.Yield()
    565 
    566     for objectID in objectIDs:
    567         x, y = bestLayout[objectID]
    568         hexCell = hexMap.objectByID[objectID]
    569         hexCell.MoveToCR(x, y)
    570 
    571     hexMap.UpdateJumpLines(refreshLayout=True)
    572     bestLayoutPenalty = FixLayout(hexMap, bestLayoutPenalty, leafs, totalObjects)
    573     if bestLayoutPenalty == 0:
    574         handmade = settings.user.ui.Get('mapHexLayout', {})
    575         if plotObjectID in handmade:
    576             del handmade[plotObjectID]
    577         settings.user.ui.Set('mapHexLayout', handmade)
    578         print('  BINGO resolved %s in %s tries' % (plotObjectID, s))
    579     else:
    580         print('  Best Layout Penalty', plotObjectID, bestLayoutPenalty)
    581     registeredPlot = settings.user.ui.Get('mapHexPlotData', {})
    582     registeredPlot[plotObjectID] = hexMap.GetCurrentLayout()
    583     settings.user.ui.Set('mapHexPlotData', registeredPlot)
    584     return bestLayoutPenalty == 0
    585 
    586 
    587 def FixLayout(hexMap, bestLayoutPenalty, leafs, totalObjects):
    588     plotObjectID = hexMap.objectID
    589     leafPenalty = TryRetractLeafs(hexMap, leafs, ignoreNoPenalty=True)
    590     if bestLayoutPenalty:
    591         if leafPenalty < bestLayoutPenalty:
    592             bestLayoutPenalty = leafPenalty
    593             print('  Best Layout Penalty after leaf retract', plotObjectID, bestLayoutPenalty)
    594         if bestLayoutPenalty:
    595             allSlots = [(0, 0)] + hexUtil.neighbours_amount_step((0, 0), totalObjects, 1, hexMap.isFlatTop)
    596             ol, ll, penalty = ScanAllOverlap(hexMap)
    597             for each in ol:
    598                 resolved = RelocateObject(hexMap, each, allSlots, sleep=False)
    599                 if resolved:
    600                     ol, ll, penalty = ScanAllOverlap(hexMap)
    601                     print('  Best Layout after object/line relocation', plotObjectID, penalty)
    602                     if penalty < bestLayoutPenalty:
    603                         bestLayoutPenalty = penalty
    604                         if not penalty:
    605                             break
    606 
    607             relocateLineOwners = set()
    608             for linePair in ll:
    609                 l1, l2 = linePair
    610                 relocateLineOwners.add(l1[0])
    611                 relocateLineOwners.add(l1[1])
    612                 relocateLineOwners.add(l2[0])
    613                 relocateLineOwners.add(l2[1])
    614 
    615             if relocateLineOwners:
    616                 for lineOwnerID in relocateLineOwners:
    617                     if lineOwnerID in hexMap.objectByID:
    618                         hexCell = hexMap.objectByID[lineOwnerID]
    619                         resolved = RelocateObject(hexMap, hexCell, allSlots, sleep=False)
    620                         if resolved:
    621                             ol, ll, penalty = ScanAllOverlap(hexMap)
    622                             print('  Best Layout after line/line relocation', plotObjectID, penalty)
    623                             if penalty < bestLayoutPenalty:
    624                                 bestLayoutPenalty = penalty
    625                                 if not penalty:
    626                                     break
    627 
    628     return bestLayoutPenalty
    629 
    630 
    631 def PlotLayout(hexMap, objectID):
    632     if objectID == const.locationUniverse:
    633         return
    634     plotObjectID = objectID
    635     import random
    636     r = random.Random()
    637     r.seed(objectID)
    638     objectData = uicore.mapObjectDataByID[objectID]
    639     radiusStep = 2
    640     allSlots = [(0, 0)] + hexUtil.neighbours_amount_step((0, 0), len(objectData), radiusStep, True)
    641     minColumn = 100000000
    642     maxColumn = -100000000
    643     minRow = 100000000
    644     maxRow = -100000000
    645     for col, row in allSlots:
    646         minColumn = min(col, minColumn)
    647         maxColumn = max(col, maxColumn)
    648         minRow = min(row, minRow)
    649         maxRow = max(row, maxRow)
    650 
    651     radius = max(abs(minColumn), maxColumn, abs(minRow), maxRow)
    652     size = radius * 2
    653     hexMap.SetMapSize(size)
    654     slots = hexUtil.neighbours_from_pos((0, 0), 0, radius, hexMap.isFlatTop)
    655     hexMap.LoadMapData(objectID, loadLayout=False)
    656     objectIDs = sorted(hexMap.objectByID.keys())
    657     hexMap.relocatedHexCells = []
    658     layoutPenalty = sys.maxint
    659     bestLayout = None
    660     bestLayoutPenalty = None
    661     for s in xrange(2500):
    662         r.shuffle(allSlots)
    663         for i, objectID in enumerate(objectIDs):
    664             hexCell = hexMap.objectByID[objectID]
    665             x, y = allSlots[i]
    666             hexCell.MoveToCR(x, y)
    667 
    668         hexMap.UpdateJumpLines(refreshLayout=True)
    669         lineOverlapPenalty = ScanForHexLineOverlaps(hexMap, layoutPenalty)
    670         if not bestLayoutPenalty or lineOverlapPenalty < bestLayoutPenalty:
    671             bestLayoutPenalty = lineOverlapPenalty
    672             bestLayout = allSlots[:]
    673             print('New best', bestLayoutPenalty)
    674         if bestLayoutPenalty == 0:
    675             break
    676         blue.synchro.Yield()
    677 
    678     print('Best result for ', plotObjectID, bestLayoutPenalty)
    679     for i, objectID in enumerate(sorted(objectIDs)):
    680         hexCell = hexMap.objectByID[objectID]
    681         x, y = bestLayout[i]
    682         hexCell.MoveToCR(x, y)
    683 
    684     hexMap.UpdateJumpLines(refreshLayout=True)
    685     return bestLayoutPenalty == 0
    686 
    687 
    688 def ResolveObjectLineOverlap(hexMap, objectLineOverlays, useSlots):
    689     someResolved = False
    690     for hexCell in objectLineOverlays:
    691         resolved = RelocateObject(hexMap, hexCell, useSlots, sleep=False)
    692         if resolved:
    693             someResolved = True
    694         blue.pyos.BeNice()
    695 
    696     return someResolved
    697 
    698 
    699 def ResolveLineLineOverlap(hexMap, lineOverlays, useSlots):
    700     done = []
    701     someResolved = False
    702     for connectionPair in lineOverlays:
    703         pairResolved = False
    704         for connectionID in connectionPair:
    705             for objectID in connectionID:
    706                 if objectID in done or objectID not in hexMap.objectByID:
    707                     continue
    708                 done.append(objectID)
    709                 hexCell = hexMap.objectByID[objectID]
    710                 resolved = RelocateObject(hexMap, hexCell, useSlots, sleep=False)
    711                 if resolved:
    712                     pairResolved = True
    713                     someResolved = True
    714                     break
    715                 blue.pyos.BeNice()
    716 
    717             if pairResolved:
    718                 break
    719 
    720     return someResolved
    721 
    722 
    723 def RelocateObject(hexMap, hexObject, useSlots, sleep = False):
    724     if sleep:
    725         blue.synchro.Sleep(500)
    726     hexMap.relocatedHexCells.append(hexObject)
    727     occupied = hexMap.GetOccupiedSlots()
    728     object_line_overlaps, line_line_overlaps, positionPenalty = ScanForOverlaps(hexMap)
    729     fallbackPosition = hexObject.GetGridPosition()
    730     for column_row in useSlots:
    731         if column_row in occupied:
    732             continue
    733         if sleep:
    734             blue.synchro.Sleep(5)
    735         hexObject.MoveToCR(*column_row)
    736         hexMap.UpdateJumpLines(refreshLayout=True)
    737         object_line_overlaps, line_line_overlaps, checkPositionPenalty = ScanForOverlaps(hexMap)
    738         if checkPositionPenalty < positionPenalty:
    739             fallbackPosition = column_row
    740             positionPenalty = checkPositionPenalty
    741             blue.pyos.BeNice()
    742             lineLineOverlapIDs = [ id for pair in line_line_overlaps for line in pair for id in line ]
    743             if hexObject not in object_line_overlaps and hexObject.objectID not in lineLineOverlapIDs:
    744                 if sleep:
    745                     blue.synchro.Sleep(500)
    746                 return True
    747 
    748     hexObject.MoveToCR(*fallbackPosition)
    749     hexMap.UpdateJumpLines(refreshLayout=True)
    750     if sleep:
    751         blue.synchro.Sleep(500)
    752     return False
    753 
    754 
    755 def ScanAllOverlap(hexMap, stopAtPenalty = None):
    756     ol, penalty = ScanForObjectLineOverlaps(hexMap, stopAtPenalty=stopAtPenalty)
    757     ll, penalty = ScanForLineLineOverlaps(hexMap, startPenalty=penalty, stopAtPenalty=stopAtPenalty)
    758     return (ol, ll, penalty)
    759 
    760 
    761 def ScanForOverlaps(hexMap, stopAtPenalty = None):
    762     penalty = 0
    763     ol, penalty = ScanForObjectLineOverlaps(hexMap, startPenalty=penalty, stopAtPenalty=stopAtPenalty)
    764     if stopAtPenalty is not None and penalty >= stopAtPenalty:
    765         return (ol, [], penalty)
    766     ll, penalty = ScanForLineLineOverlaps(hexMap, startPenalty=penalty, stopAtPenalty=stopAtPenalty)
    767     return (ol, ll, penalty)
    768 
    769 
    770 def ScanForObjectLineOverlaps(hexMap, startPenalty = 0, stopAtPenalty = None):
    771     penalty = startPenalty
    772     object_line_overlaps = []
    773     objectIDs = sorted(hexMap.objectByID.keys())
    774     for objectID in objectIDs:
    775         uiObject = hexMap.objectByID[objectID]
    776         objPos = (uiObject.left + hexMap.width / 2.0, uiObject.top + hexMap.height / 2.0)
    777         for connectionID, line in hexMap.connectionsByID.iteritems():
    778             if objectID in connectionID:
    779                 continue
    780             if not line.renderObject.vertices:
    781                 continue
    782             p1x, p1y = line.renderObject.vertices[0].position
    783             p2x, p2y = line.renderObject.vertices[1].position
    784             pointOnLine = hexUtil.closest_point_on_seg((p1x, p1y), (p2x, p2y), objPos)
    785             dist_v = geo2.Vec2Subtract(pointOnLine, objPos)
    786             if geo2.Vec2Length(dist_v) < uiObject.hexSize:
    787                 object_line_overlaps.append(uiObject)
    788                 penalty += 100
    789                 if stopAtPenalty is not None and penalty >= stopAtPenalty:
    790                     return (object_line_overlaps, penalty)
    791 
    792     return (object_line_overlaps, penalty)
    793 
    794 
    795 def ScanForLineLineOverlaps(hexMap, startPenalty = 0, stopAtPenalty = None):
    796     penalty = startPenalty
    797     line_line_overlaps = []
    798     done = []
    799     lineIDs = hexMap.connectionsByID.keys()
    800     for connectionID1 in lineIDs:
    801         line1 = hexMap.connectionsByID[connectionID1]
    802         if not line1.renderObject.vertices:
    803             continue
    804         for connectionID2 in lineIDs:
    805             if connectionID1 == connectionID2:
    806                 continue
    807             if (connectionID1, connectionID2) in done:
    808                 continue
    809             line2 = hexMap.connectionsByID[connectionID2]
    810             if not line2.renderObject.vertices:
    811                 continue
    812             done.append((connectionID1, connectionID2))
    813             done.append((connectionID2, connectionID1))
    814             p11 = line1.renderObject.vertices[0].position
    815             p12 = line1.renderObject.vertices[1].position
    816             p21 = line2.renderObject.vertices[0].position
    817             p22 = line2.renderObject.vertices[1].position
    818             try:
    819                 crossPoint = hexUtil.intersect_line_segments((p11, p12), (p21, p22))
    820             except ValueError:
    821                 crossPoint = True
    822 
    823             if crossPoint:
    824                 line_line_overlaps.append((connectionID1, connectionID2))
    825                 penalty += 1
    826                 if stopAtPenalty is not None and penalty >= stopAtPenalty:
    827                     return (line_line_overlaps, penalty)
    828 
    829     return (line_line_overlaps, penalty)
    830 
    831 
    832 def ScanForHexLineOverlaps(hexMap, lastPenalty = None):
    833     done = []
    834     currentPenalty = 0
    835     lineIDs = hexMap.hexGridConnectionsByID.keys()
    836     for connectionID1 in lineIDs:
    837         line1, shadowLine = hexMap.hexGridConnectionsByID[connectionID1]
    838         for connectionID2 in lineIDs:
    839             if (connectionID1, connectionID2) in done:
    840                 continue
    841             line2, shadowLine2 = hexMap.hexGridConnectionsByID[connectionID2]
    842             if line1 is line2:
    843                 continue
    844             done.append((connectionID1, connectionID2))
    845             done.append((connectionID2, connectionID1))
    846             crossPoints = line1.GetCrossPointsWithLine(line2)
    847             currentPenalty += len(crossPoints)
    848             if lastPenalty is not None and currentPenalty > lastPenalty:
    849                 return currentPenalty
    850 
    851     return currentPenalty
    852 
    853 
    854 def GetPlotDataForObject(objectID, ignoreFixed = None):
    855     if ignoreFixed is None:
    856         ignoreFixed = settings.user.ui.Get('mapDebugViewIgnoreFixed', 0)
    857     registeredPlot = settings.user.ui.Get('mapHexPlotData', {})
    858     handmade = settings.user.ui.Get('mapHexLayout', {})
    859     if objectID == const.locationUniverse:
    860         plotData = FIXED_LAYOUT_BY_ID[objectID]
    861     elif not ignoreFixed and objectID in handmade:
    862         plotData = handmade[objectID]
    863     elif objectID in registeredPlot:
    864         plotData = registeredPlot[objectID]
    865     else:
    866         plotData = None
    867     return plotData
    868 
    869 
    870 def GetHexMapBoundaries(objectID):
    871     minColumn = sys.maxint
    872     maxColumn = -sys.maxint
    873     minRow = sys.maxint
    874     maxRow = -sys.maxint
    875     plotData = GetPlotDataForObject(objectID)
    876     if plotData:
    877         for childID, column_row in plotData.iteritems():
    878             col, row = column_row
    879             minColumn = min(col, minColumn)
    880             maxColumn = max(col, maxColumn)
    881             minRow = min(row, minRow)
    882             maxRow = max(row, maxRow)
    883 
    884     else:
    885         raise RuntimeError('No map layout found for objectID %s' % objectID)
    886     return (minColumn,
    887      minRow,
    888      maxColumn,
    889      maxRow)
    890 
    891 
    892 def GetHexMapSizeForObject(objectID):
    893     minColumn, minRow, maxColumn, maxRow = GetHexMapBoundaries(objectID)
    894     return int(round(max(abs(minColumn), maxColumn, abs(minRow), maxRow) + 0.5)) * 2
    895 
    896 
    897 def PrepareCombinedMap(objectID):
    898     rootLayout = GetPlotDataForObject(objectID)
    899     rootLayout, rootsize = AutoFitLayout(rootLayout)
    900     isFlatTop = True
    901     maxChildSize = 0
    902     for childID, childGridPosition in rootLayout.iteritems():
    903         childLayout = GetPlotDataForObject(childID)
    904         childLayout, size = AutoFitLayout(childLayout)
    905         maxChildSize = max(maxChildSize, size)
    906 
    907     maxOffset = 0
    908     layoutData = {}
    909     connectionData = []
    910     exitConnectionData = []
    911     for childID, childGridPosition in rootLayout.iteritems():
    912         column, row = childGridPosition
    913         cX, cY = hexUtil.hex_slot_center_position(column, row, isFlatTop, maxChildSize / 2)
    914         px_hx = hexUtil.pixel_to_hex(cX, cY, 1, isFlatTop)
    915         ax_cu = hexUtil.axial_to_cube_coordinate(*px_hx)
    916         ax_cu_rounded = hexUtil.hex_round(*ax_cu)
    917         cu_ax = hexUtil.cube_to_odd_q_axial_coordinate(*ax_cu_rounded)
    918         childLayout = GetPlotDataForObject(childID)
    919         childLayout, size = AutoFitLayout(childLayout)
    920         connectionData += uicore.mapConnectionDataByID.get(childID, [])
    921         for grandChildID, grandChildGridPosition in childLayout.iteritems():
    922             childColumn = cu_ax[0] + grandChildGridPosition[0]
    923             childRow = cu_ax[1] + grandChildGridPosition[1]
    924             layoutData[grandChildID] = (childColumn, childRow)
    925             maxOffset = max(maxOffset, childColumn, abs(childColumn), childRow, abs(childRow))
    926 
    927     connections = uicore.mapExitConnectionsByID.get(objectID, None)
    928     if connections:
    929         for fromID, fromParentID, toID, toParentID in connections:
    930             exitConnectionData.append((fromID, toID))
    931 
    932     combinedLayout, size = AutoFitLayout(layoutData)
    933     return (combinedLayout,
    934      connectionData,
    935      exitConnectionData,
    936      size)
    937 
    938 
    939 def AutoFitLayout(layout):
    940     minColumn = sys.maxint
    941     maxColumn = -sys.maxint
    942     minRow = sys.maxint
    943     maxRow = -sys.maxint
    944     for objectID, column_row in layout.iteritems():
    945         col, row = column_row
    946         minColumn = min(col, minColumn)
    947         maxColumn = max(col, maxColumn)
    948         minRow = min(row, minRow)
    949         maxRow = max(row, maxRow)
    950 
    951     offsetColumn, offsetRows = -(minColumn + maxColumn) / 2, -(minRow + maxRow) / 2
    952     oddColumnOffset = offsetColumn & 1
    953     newLayout = {}
    954     size = 0
    955     for objectID, (column, row) in layout.iteritems():
    956         if oddColumnOffset and not column & 1:
    957             row -= 1
    958         newLayout[objectID] = (column + offsetColumn, row + offsetRows)
    959         size = max(size, abs(column + offsetColumn), abs(row + offsetRows))
    960 
    961     size = int(round(size + 0.5)) * 2
    962     return (newLayout, size)