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)