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

preview.py (50888B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\preview.py
      2 import uthread2
      3 from brennivin.itertoolsext import first, first_or_default
      4 from carbon.common.script.util.format import FmtDist
      5 from carbonui.control.scrollContainer import ScrollContainer
      6 from carbonui.primitives.container import Container
      7 from carbonui.primitives.containerAutoSize import ContainerAutoSize
      8 from carbonui.primitives.sprite import Sprite
      9 from characterdata.npccharacters import get_npc_character
     10 from characterdata.races import get_race_name
     11 from cosmetics.client.shipSkinLicensesSvc import get_ship_skin_license_svc
     12 from cosmetics.common.ships.skins.static_data.skin_type import ShipSkinType
     13 from eve.client.script.environment.model.turretSet import TurretSet
     14 from eve.client.script.paperDoll.paperDollImpl import Doll, Factory
     15 from eve.client.script.paperDoll.paperDollSpawnWrappers import PaperDollCharacter
     16 from eve.client.script.ui.camera.cameraUtil import GetModelLookAtOffset
     17 from carbonui.button.group import ButtonGroup
     18 from eve.client.script.ui.control.eveLabel import EveCaptionMedium, EveHeaderSmall, Label
     19 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel
     20 from carbonui.control.window import Window
     21 from player_messaging.client.ui_message import message_player
     22 from eve.client.script.ui.control.scenecontainer import SceneContainerBaseNavigation, SceneContainer
     23 from eve.client.script.ui.control.themeColored import FillThemeColored
     24 from eve.client.script.ui.login.charcreation.ccUtil import GenderIDToPaperDollGender
     25 from eve.client.script.ui.shared.skins.controller import SkinPanelController, ThirdPartySkinWrapper, GetSkinnedShipModel
     26 from eve.client.script.ui.shared.skins.skinPanel import SkinPanel
     27 from eve.client.script.ui.station.assembleModularShip import AssembleShip
     28 from eve.client.script.ui.util.uiComponents import Component, ButtonEffect
     29 from eve.common.script.paperDoll.paperDollDefinitions import ACCESSORIES_CATEGORIES, BODY_CATEGORIES, DOLL_PARTS, GENDER, HEAD_CATEGORIES, LOD_SKIN
     30 from eve.common.script.sys import idCheckers
     31 from eve.common.script.sys.eveCfg import IsPreviewable
     32 from eve.common.script.sys.idCheckers import IsShipType
     33 import evetypes
     34 from eveexceptions import UserError
     35 from fsdBuiltData.common.graphicIDs import GetGraphicFile, GetSofFactionName
     36 from inventorycommon.util import IsModularShip
     37 from localization import GetByLabel
     38 from publicGateway.grpc.exceptions import GenericException
     39 from stackless_response_router.exceptions import TimeoutException
     40 from utillib import KeyVal
     41 import mathext
     42 import carbon.common.script.sys.service as service
     43 import carbonui.const as uiconst
     44 import charactercreator.const as ccConst
     45 import eve.common.lib.appConst as appConst
     46 import evegraphics.settings as gfxsettings
     47 import evegraphics.utils as gfxutils
     48 import eveSpaceObject
     49 import eveSpaceObject.spaceobjanimation as soanimation
     50 import geo2
     51 import inventorycommon.const as invconst
     52 import localization
     53 import log
     54 import math
     55 import re
     56 import sys
     57 import trinity
     58 import uthread
     59 import inventorycommon.typeHelpers
     60 import charactercreator.client.animparams as animparams
     61 from eve.client.script.environment.sofService import GetSofService
     62 from carbonui.uicore import uicore
     63 from eveservices.menu import GetMenuService
     64 from functools import reduce
     65 MESH_NAMES_BY_GROUPID = {invconst.groupApparelEyewear: [ACCESSORIES_CATEGORIES.ACCESSORIES],
     66  invconst.groupApparelProsthetics: [ACCESSORIES_CATEGORIES.SLEEVESLOWER],
     67  invconst.groupApparelTattoos: [ACCESSORIES_CATEGORIES.SLEEVESLOWER],
     68  invconst.groupApparelPiercings: [],
     69  invconst.groupApparelScars: [BODY_CATEGORIES.SCARS],
     70  invconst.groupApparelMidLayer: [BODY_CATEGORIES.TOPINNER],
     71  invconst.groupApparelOuter: [BODY_CATEGORIES.OUTER],
     72  invconst.groupApparelTops: [BODY_CATEGORIES.TOPINNER],
     73  invconst.groupApparelBottoms: [BODY_CATEGORIES.BOTTOMOUTER],
     74  invconst.groupApparelFootwear: [BODY_CATEGORIES.FEET],
     75  invconst.groupApparelHairStyles: [DOLL_PARTS.HAIR, DOLL_PARTS.HEAD],
     76  invconst.groupApparelMakeup: [HEAD_CATEGORIES.MAKEUP],
     77  invconst.groupApparelAugmentations: [DOLL_PARTS.HEAD],
     78  invconst.groupApparelMasks: [ACCESSORIES_CATEGORIES.ACCESSORIES]}
     79 MANNEQUIN_RES_BY_GENDER = {GENDER.MALE: 'res:/Graphics/Character/DNAFiles/Mannequin/MaleMannequin.prs',
     80  GENDER.FEMALE: 'res:/Graphics/Character/DNAFiles/Mannequin/FemaleMannequin.prs'}
     81 PAPERDOLL_CATEGORIES_COVERING = {ccConst.bottommiddle: [ccConst.bottomouter]}
     82 
     83 def GetPaperDollResource(typeID, gender = None):
     84     assets = filter(lambda a: a.typeID == typeID, cfg.paperdollResources)
     85     if len(assets) == 0:
     86         log.LogWarn('PreviewWnd::PreviewType - No asset matched the typeID {}'.format(typeID))
     87         return None
     88     default_asset = first(assets)
     89     unisex_asset = first_or_default(assets, lambda a: a.resGender is None, default_asset)
     90     return first_or_default(assets, lambda a: a.resGender == gender, unisex_asset)
     91 
     92 
     93 def SetupAnimations(model):
     94     if not model.controllers:
     95         model.ChainAnimationEx('NormalLoop', 0, 0, 1.0)
     96     soanimation.TriggerDefaultStates(model)
     97 
     98 
     99 class Preview(service.Service):
    100     __guid__ = 'svc.preview'
    101     __servicename__ = 'preview'
    102     __displayname__ = 'Preview Service'
    103     __exportedcalls__ = {'PreviewType': [],
    104      'PreviewCharacter': [],
    105      'DisplayCharacter': []}
    106     __dependencies__ = []
    107 
    108     def Run(self, memStream = None):
    109         service.Service.Run(self, memStream=memStream)
    110         self.state = service.SERVICE_RUNNING
    111 
    112     def PreviewType(self, typeID, subsystems = None, itemID = None, animate = True, otherTypeID = None, skin_state = None, **kwargs):
    113         categoryID = evetypes.GetCategoryID(typeID)
    114         shipTypeID = None
    115         skin = None
    116         if categoryID == invconst.categoryShip:
    117             shipTypeID = typeID
    118         elif categoryID == invconst.categoryShipSkin:
    119             skin = sm.GetService('cosmeticsSvc').GetSkinByLicenseType(typeID)
    120             shipTypeID = otherTypeID if otherTypeID and otherTypeID in skin.types else skin.types[0]
    121         if shipTypeID:
    122             params = {'skin': skin}
    123             from eve.client.script.ui.shared.info.shipInfoConst import TAB_SKINS
    124             sm.GetService('info').ShowInfo(typeID=shipTypeID, itemID=itemID, selectTabType=TAB_SKINS, params=params)
    125             return
    126         wnd = PreviewWnd.Open(typeID=typeID)
    127         if wnd:
    128             wnd.PreviewType(typeID=typeID, subsystems=subsystems, itemID=itemID, animate=animate, otherTypeID=otherTypeID, skin_state=skin_state, **kwargs)
    129         return wnd
    130 
    131     def PreviewCharacter(self, charID):
    132         if charID in appConst.auraAgentIDs:
    133             return
    134         dna = sm.RemoteSvc('paperDollServer').GetPaperDollData(charID)
    135         if dna is None:
    136             raise UserError('CharacterHasNoDNA', {'charID': charID})
    137         wnd = PreviewCharacterWnd.GetIfOpen()
    138         if wnd:
    139             wnd.PreviewCharacter(charID=charID, dna=dna)
    140         else:
    141             wnd = PreviewCharacterWnd.Open(charID=charID, dna=dna)
    142         return wnd
    143 
    144     def DisplayCharacter(self, charID):
    145         dna = sm.RemoteSvc('paperDollServer').GetPaperDollData(charID)
    146         if dna is None:
    147             raise UserError('CharacterHasNoDNA', {'charID': charID})
    148         wnd = PreviewCharacterWnd.GetIfOpen()
    149         if wnd:
    150             wnd.PreviewCharacter(charID=charID, dna=dna)
    151         else:
    152             PreviewCharacterWnd.Open(charID=charID, dna=dna)
    153 
    154 
    155 @Component(ButtonEffect(opacityIdle=0.0, opacityHover=0.5, opacityMouseDown=1.0, bgElementFunc=lambda parent, _: parent.hilite, audioOnEntry=uiconst.SOUND_ENTRY_HOVER, audioOnClick=uiconst.SOUND_ENTRY_SELECT))
    156 
    157 class SidePanelButton(ContainerAutoSize):
    158     default_state = uiconst.UI_NORMAL
    159 
    160     def ApplyAttributes(self, attributes):
    161         super(SidePanelButton, self).ApplyAttributes(attributes)
    162         self.onClick = attributes.onClick
    163         self.expanded = attributes.get('expanded', False)
    164         Sprite(parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/preview/tab.png', width=20, height=80)
    165         self.hilite = Sprite(parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/preview/tab-hilite.png', width=20, height=80, opacity=0.0)
    166         rotation = 0 if self.expanded else math.pi
    167         self.tabIcon = Sprite(parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, left=2, top=32, texturePath='res:/UI/Texture/classes/preview/tab-icon.png', width=16, height=16, rotation=rotation)
    168 
    169     def OnClick(self, *args):
    170         self.expanded = not self.expanded
    171         self.AnimClick()
    172         self.onClick()
    173 
    174     def AnimClick(self):
    175         rotation = 0 if self.expanded else math.pi
    176         uicore.animations.MorphScalar(self.tabIcon, 'rotation', startVal=self.tabIcon.rotation, endVal=rotation, duration=0.3)
    177 
    178 
    179 class PreviewWnd(Window):
    180     __guid__ = 'form.PreviewWnd'
    181     default_windowID = 'previewWnd'
    182     default_minSize = (420, 330)
    183     default_caption = localization.GetByLabel('UI/Preview/PreviewCaption')
    184     PANEL_WIDTH = 416
    185 
    186     def ApplyAttributes(self, attributes):
    187         Window.ApplyAttributes(self, attributes)
    188         self.typeID = attributes.typeID
    189         width = self.PANEL_WIDTH if self.IsPanelExpanded() else 0
    190         self.sidePanelWrapper = Container(name='sidePanelWrapper', parent=self.sr.main, align=uiconst.TOLEFT, width=width, bgColor=(1.0, 1.0, 1.0, 0.05), padLeft=-16, padBottom=-16, clipChildren=True)
    191         self.sidePanel = Container(name='SidePanel', parent=self.sidePanelWrapper, align=uiconst.TOLEFT, width=self.PANEL_WIDTH, padding=8)
    192         self.skinController = SkinPanelController(hullTypeID=None)
    193         self.skinPanel = SkinPanel(parent=self.sidePanel, align=uiconst.TOLEFT, width=self.PANEL_WIDTH - 16, controller=self.skinController, settingsPrefix='Preview_SkinPanel', logContext='PreviewWindow')
    194         mainCont = Container(name='MainContainer', parent=self.sr.main, align=uiconst.TOALL, padding=(16, 0, -16, -16))
    195         opacity = 1.0 if self.IsPanelEnabled() else 0.0
    196         self.sidePanelButton = SidePanelButton(parent=mainCont, align=uiconst.CENTERLEFT, opacity=opacity, onClick=self.ToggleSidePanel, expanded=self.IsPanelExpanded())
    197         self.loadingWheel = LoadingWheel(parent=mainCont, align=uiconst.CENTER, state=uiconst.UI_DISABLED)
    198         self.previewContFill = FillThemeColored(parent=mainCont, align=uiconst.TOALL)
    199         overlayCont = Container(name='overlayCont', parent=mainCont, padding=2, clipChildren=1)
    200         self.title = EveCaptionMedium(text='', parent=overlayCont, align=uiconst.TOTOP, padding=(17, 4, 17, 0), state=uiconst.UI_NORMAL)
    201         self.title.GetMenu = self.GetShipMenu
    202         self.title.expandOnLeft = 1
    203         self.subtitle = EveHeaderSmall(text='', parent=overlayCont, align=uiconst.TOTOP, padding=(19, 0, 17, 0), state=uiconst.UI_DISABLED)
    204         self.descCont = ScrollContainer(name='DescriptionContainer', parent=mainCont, align=uiconst.TOBOTTOM_PROP, height=0.3, bgColor=(0.0, 0.0, 0.0, 0.3), state=uiconst.UI_HIDDEN)
    205         self.previewContainer = PreviewContainer(parent=mainCont, align=uiconst.TOALL, OnStartLoading=self.OnStartLoading, OnStopLoading=self.OnStopLoading)
    206         self.previewContainer.navigation.OnDropData = self.OnDropData
    207         self.desc = Label(parent=self.descCont, padding=12, fontsize=12, align=uiconst.TOTOP)
    208 
    209     def IsPanelExpanded(self):
    210         return self.IsPanelEnabled() and settings.user.ui.Get('previewPanel', 1)
    211 
    212     def IsPanelEnabled(self):
    213         categoryID = evetypes.GetCategoryID(self.typeID)
    214         if categoryID in (invconst.categoryShip, invconst.categoryShipSkin):
    215             self.UpdateSidePanelButton(isEnabled=True)
    216             return True
    217         self.UpdateSidePanelButton(isEnabled=False)
    218         return False
    219 
    220     def UpdateSidePanelButton(self, isEnabled):
    221         if not hasattr(self, 'sidePanelButton'):
    222             return
    223         if not self.sidePanelButton:
    224             return
    225         if isEnabled:
    226             self.sidePanelButton.Enable()
    227         else:
    228             self.sidePanelButton.Disable()
    229 
    230     def ToggleSidePanel(self):
    231         isExpanded = not self.IsPanelExpanded()
    232         settings.user.ui.Set('previewPanel', isExpanded)
    233         self.UpdateSidePanel(isExpanded)
    234 
    235     def UpdateSidePanel(self, expanded = None):
    236         if expanded is None:
    237             expanded = settings.user.ui.Get('previewPanel', 1)
    238         width = self.PANEL_WIDTH if expanded else 0
    239         uicore.animations.MorphScalar(self.sidePanelWrapper, 'width', startVal=self.sidePanelWrapper.width, endVal=width, duration=0.3)
    240 
    241     def OnStartLoading(self, previewCont):
    242         uicore.animations.FadeIn(self.loadingWheel, duration=0.4)
    243         uicore.animations.FadeIn(self.previewContFill, duration=0.05, sleep=True)
    244         self.ClearText()
    245 
    246     def OnStopLoading(self, previewCont, success):
    247         uicore.animations.FadeOut(self.loadingWheel, duration=0.2)
    248         if success:
    249             uicore.animations.FadeOut(self.previewContFill, duration=0.4)
    250             self.UpdateText()
    251 
    252     def UpdateText(self):
    253         context = self.previewContainer.context
    254         if not hasattr(context, 'typeID'):
    255             return
    256         groupID = evetypes.GetGroupID(context.typeID)
    257         categoryID = evetypes.GetCategoryID(context.typeID)
    258         title = evetypes.GetName(context.typeID)
    259         if hasattr(context, 'itemID'):
    260             bp = sm.GetService('michelle').GetBallpark()
    261             if bp:
    262                 slim = bp.GetInvItem(context.itemID)
    263                 if slim:
    264                     title = slim.name
    265         self.title.text = title
    266         subtitle = ''
    267         if categoryID != invconst.categoryApparel:
    268             scene = self.previewContainer.sceneContainer.scene
    269             model = first_or_default(getattr(scene, 'objects', []), None)
    270             if model:
    271                 radius = round(model.GetBoundingSphereRadius() * 2, 0)
    272                 if groupID in invconst.turretModuleGroups:
    273                     subtitle = localization.GetByLabel('UI/Preview/ShipSubLabelNoRace', groupName=evetypes.GetGroupName(context.typeID), length=FmtDist(radius))
    274                 else:
    275                     raceID = evetypes.GetRaceID(context.typeID)
    276                     try:
    277                         subtitle = localization.GetByLabel('UI/Preview/ShipSubLabel', raceName=get_race_name(raceID), groupName=evetypes.GetGroupName(context.typeID), length=FmtDist(radius))
    278                     except KeyError:
    279                         subtitle = localization.GetByLabel('UI/Preview/ShipSubLabelNoRace', groupName=evetypes.GetGroupName(context.typeID), length=FmtDist(radius))
    280 
    281         self.subtitle.text = subtitle
    282         if categoryID == invconst.categoryApparel:
    283             self.descCont.Show()
    284             description = evetypes.GetDescription(context.typeID) or ''
    285             description = re.sub('<b>|</b>|\\r', '', description)
    286             description = re.sub('\\n', '<br>', description)
    287             self.desc.text = description
    288 
    289     def GetShipMenu(self, *args):
    290         return GetMenuService().GetMenuFromItemIDTypeID(None, self.typeID, includeMarketDetails=True).filter([localization.GetByLabel('UI/Preview/Preview')])
    291 
    292     def OnDropData(self, dragObj, nodes):
    293         super(PreviewWnd, self).OnDropData(dragObj, nodes)
    294         node = first(nodes)
    295         typeID = None
    296         if hasattr(node, 'item') and hasattr(node.item, 'typeID'):
    297             typeID = node.item.typeID
    298         elif hasattr(node, 'typeID'):
    299             typeID = node.typeID
    300         itemID = None
    301         if hasattr(node, 'item') and hasattr(node.item, 'itemID'):
    302             itemID = node.item.itemID
    303         elif hasattr(node, 'itemID'):
    304             itemID = node.itemID
    305         if typeID:
    306             if IsPreviewable(typeID):
    307                 self.PreviewType(typeID, itemID=itemID)
    308             else:
    309                 raise UserError('PreviewNoModel')
    310 
    311     def PreviewType(self, typeID, subsystems = None, itemID = None, animate = True, otherTypeID = None, skin_state = None, **kwargs):
    312         uthread.new(self._PreviewType, typeID, subsystems, itemID, animate, otherTypeID, skin_state, **kwargs)
    313 
    314     def _PreviewType(self, typeID, subsystems, itemID, animate, otherTypeID = None, skin_state = None, **kwargs):
    315         self.typeID = typeID
    316         self.BringToFront()
    317         if evetypes.GetCategoryID(typeID) == invconst.categoryApparel:
    318             self.SetMinSize([320, 470])
    319             self.SetMaxSize([800, 950])
    320         else:
    321             self.SetMinSize([660, 320])
    322             self.SetMaxSize([None, None])
    323         if not self.IsPanelEnabled():
    324             uicore.animations.FadeOut(self.sidePanelButton, duration=0.3)
    325             self.UpdateSidePanel(expanded=False)
    326         if self.IsPanelEnabled():
    327             uicore.animations.FadeIn(self.sidePanelButton, duration=0.3)
    328             self.UpdateSidePanel()
    329         if IsModularShip(typeID):
    330             kv = KeyVal(typeID=typeID)
    331             wnd = AssembleShip.GetIfOpen('PreviewSubSystems')
    332             if wnd:
    333                 wnd.UpdateShip(kv, subsystems)
    334             else:
    335                 wnd = AssembleShip.Open(windowID='PreviewSubSystems', ship=kv, groupIDs=None, isPreview=True, setselected=subsystems)
    336             subsystems = wnd.getSubSystems()
    337         else:
    338             self.CloseSubSystemWnd()
    339         newScene = self.previewContainer.PreviewType(typeID, subsystems=subsystems, itemID=itemID, controller=getattr(self, 'skinController', None), otherTypeID=otherTypeID, skin_state=skin_state, **kwargs)
    340         groupID = evetypes.GetGroupID(typeID)
    341         categoryID = evetypes.GetCategoryIDByGroup(groupID)
    342         if categoryID == invconst.categoryShip:
    343             self.skinPanel.SetPreviewType(typeID)
    344         elif categoryID == invconst.categoryShipSkin:
    345             skin = sm.GetService('cosmeticsSvc').GetSkinByLicenseType(typeID)
    346             shipTypeID = otherTypeID if otherTypeID and otherTypeID in skin.types else skin.types[0]
    347             self.skinPanel.SetPreviewType(shipTypeID)
    348         if newScene and animate:
    349             self.previewContainer.AnimEntry()
    350 
    351     def ClearText(self):
    352         self.title.text = ''
    353         self.subtitle.text = ''
    354         self.desc.SetText('')
    355         self.descCont.Hide()
    356 
    357     def BringToFront(self):
    358         self.Maximize()
    359         wnd = AssembleShip.GetIfOpen(windowID='PreviewSubSystems')
    360         if wnd and wnd.parent.children.index(wnd) > 1:
    361             wnd.Maximize()
    362 
    363     def _OnResize(self, *args, **kw):
    364         self.previewContainer.UpdateViewPort()
    365 
    366     def CloseSubSystemWnd(self):
    367         AssembleShip.CloseIfOpen(windowID='PreviewSubSystems')
    368 
    369     def Close(self, setClosed = False, *args, **kwds):
    370         Window.Close(self, setClosed, *args, **kwds)
    371         self.CloseSubSystemWnd()
    372 
    373     def OnStartMinimize_(self, *args):
    374         self.previewContainer.Hide()
    375 
    376     def OnStartMaximize_(self, *args):
    377         self.previewContainer.Show()
    378 
    379 
    380 class PreviewCharacterWnd(Window):
    381     __guid__ = 'form.PreviewCharacterWnd'
    382     default_windowID = 'previewCharacterWnd'
    383     default_minSize = (420, 320)
    384 
    385     def ApplyAttributes(self, attributes):
    386         Window.ApplyAttributes(self, attributes)
    387         from eve.client.script.ui.shared.neocom.characterSheetWindow import CharacterSheetWindow
    388         CharacterSheetWindow.CloseIfOpen()
    389         self.charID = attributes.charID
    390         self.dna = attributes.dna
    391         self.btnGroup = ButtonGroup(parent=self.sr.main, idx=0)
    392         self.btnGroup.AddButton(localization.GetByLabel('UI/Preview/ViewPortrait'), self.SwitchToPortrait, (self.charID,))
    393         self.previewContainer = PreviewContainer(parent=self.sr.main)
    394         self.PreviewCharacter(self.charID, dna=self.dna)
    395 
    396     def PreviewCharacter(self, charID, dna):
    397         self.charID = charID
    398         self.dna = dna
    399         caption = localization.GetByLabel('UI/InfoWindow/PortraitCaption', character=charID)
    400         self.SetCaption(caption)
    401         uthread.new(self.previewContainer.PreviewCharacter, charID, dna=dna)
    402         self.Maximize()
    403 
    404     def SwitchToPortrait(self, charID):
    405         from eve.client.script.ui.shared.portraitWindow.portraitWindow import PortraitWindow
    406         PortraitWindow.CloseIfOpen()
    407         portraitWnd = PortraitWindow.Open(charID=charID)
    408         portraitWnd.Maximize()
    409         self.CloseByUser()
    410 
    411     def _OnResize(self, *args, **kw):
    412         self.previewContainer.UpdateViewPort()
    413 
    414 
    415 class InvalidPreviewType(Exception):
    416     pass
    417 
    418 
    419 class PreviewContainerClosing(Exception):
    420     pass
    421 
    422 
    423 class PreviewNavigation(SceneContainerBaseNavigation):
    424     default_cursor = uiconst.UICURSOR_CCALLDIRECTIONS
    425     default_state = uiconst.UI_NORMAL
    426 
    427     def UpdateCursor(self):
    428         if uicore.uilib.rightbtn and not uicore.uilib.leftbtn and self.sr.sceneContainer.verticalPanEnabled:
    429             self.cursor = uiconst.UICURSOR_CCUPDOWN
    430         else:
    431             self.cursor = uiconst.UICURSOR_CCALLDIRECTIONS
    432 
    433     def OnMouseDown(self, *args):
    434         SceneContainerBaseNavigation.OnMouseDown(self, *args)
    435         self.UpdateCursor()
    436 
    437     def OnMouseUp(self, *args):
    438         SceneContainerBaseNavigation.OnMouseUp(self, *args)
    439         self.UpdateCursor()
    440 
    441     def OnMouseMove(self, *args):
    442         if self.sr.sceneContainer.verticalPanEnabled and uicore.uilib.rightbtn:
    443             cameraDistance = self.sr.sceneContainer.camera.GetZoomDistance()
    444             delta = uicore.uilib.dy * 0.0006 * cameraDistance
    445             y = self.sr.sceneContainer.verticalPan
    446             self.sr.sceneContainer.verticalPan = y + delta
    447         else:
    448             SceneContainerBaseNavigation.OnMouseMove(self, *args)
    449 
    450 
    451 class PreviewSceneContainer(SceneContainer):
    452     default_state = uiconst.UI_DISABLED
    453 
    454     def ApplyAttributes(self, attributes):
    455         super(PreviewSceneContainer, self).ApplyAttributes(attributes)
    456         self._minY = None
    457         self._maxY = None
    458 
    459     @property
    460     def verticalPanLimits(self):
    461         return (self._minY, self._maxY)
    462 
    463     @verticalPanLimits.setter
    464     def verticalPanLimits(self, limits):
    465         if limits is None:
    466             limits = (None, None)
    467         minY, maxY = limits
    468         if minY > maxY:
    469             minY, maxY = maxY, minY
    470         self._minY = minY
    471         self._maxY = maxY
    472 
    473     @property
    474     def verticalPanEnabled(self):
    475         return self._minY is not None and self._maxY is not None
    476 
    477     @property
    478     def verticalPan(self):
    479         if self.camera:
    480             return self.camera.atPosition[1]
    481 
    482     @verticalPan.setter
    483     def verticalPan(self, y):
    484         if self.verticalPanEnabled:
    485             y = mathext.clamp(y, self._minY, self._maxY)
    486             x, _, z = self.camera.atPosition
    487             self.camera.SetAtPosition((x, y, z))
    488 
    489 
    490 class PreviewContainer(Container):
    491     __notifyevents__ = ['OnGraphicSettingsChanged', 'OnSetDevice']
    492 
    493     def ApplyAttributes(self, attributes):
    494         Container.ApplyAttributes(self, attributes)
    495         sm.RegisterNotify(self)
    496         self.context = None
    497         self.startLoadingCallback = attributes.get('OnStartLoading', None)
    498         self.stopLoadingCallback = attributes.get('OnStopLoading', None)
    499         self.skinController = None
    500         self.loadingWheel = LoadingWheel(parent=self, align=uiconst.CENTER, state=uiconst.UI_DISABLED)
    501         self.loadingWheel.opacity = 0.0
    502         self.ConstructSceneContainer()
    503         self.sceneContainer.Startup()
    504         self.navigation = PreviewNavigation(parent=self)
    505         self.navigation.Startup(self.sceneContainer)
    506 
    507     def ConstructSceneContainer(self):
    508         self.sceneContainer = PreviewSceneContainer(parent=self, align=uiconst.TOALL)
    509 
    510     def Close(self):
    511         super(PreviewContainer, self).Close()
    512         self._Cleanup()
    513 
    514     def _OnStartLoading(self):
    515         if self.startLoadingCallback:
    516             self.startLoadingCallback(self)
    517         else:
    518             uicore.animations.FadeIn(self.loadingWheel, duration=0.4)
    519 
    520     def _OnStopLoading(self, success):
    521         if self.stopLoadingCallback:
    522             self.stopLoadingCallback(self, success)
    523         else:
    524             uicore.animations.FadeOut(self.loadingWheel, duration=0.4)
    525 
    526     def PreviewType(self, typeID, **kwargs):
    527         if not IsPreviewable(typeID):
    528             raise InvalidPreviewType('%s (%s) is not previewable' % (evetypes.GetName(typeID), typeID))
    529         groupID = evetypes.GetGroupID(typeID)
    530         categoryID = evetypes.GetCategoryIDByGroup(groupID)
    531         subsystems = kwargs.get('subsystems', None)
    532         if categoryID == invconst.categoryApparel:
    533             return self.PreviewApparel(typeID, gender=kwargs.get('gender'), background=kwargs.get('background'))
    534         elif groupID in invconst.turretModuleGroups:
    535             return self.PreviewTurret(typeID, scenePath=kwargs.get('scenePath'))
    536         elif categoryID == invconst.categoryShip:
    537             return self.PreviewShip(typeID, **kwargs)
    538         elif categoryID == invconst.categoryShipSkin:
    539             return self.PreviewSkin(typeID, **kwargs)
    540         else:
    541             return self.PreviewSpaceEntity(typeID, itemID=kwargs.get('itemID', None), subsystems=subsystems, scenePath=kwargs.get('scenePath', None))
    542 
    543     def PreviewApparel(self, typeID, gender = None, background = None):
    544         from eve.client.script.ui.shared.neocom.characterSheetWindow import CharacterSheetWindow
    545         CharacterSheetWindow.CloseIfOpen()
    546         context = ApparelSceneContext(typeID, gender=gender, background=background)
    547         return self.LoadScene(context)
    548 
    549     def PreviewCharacter(self, charID, dna = None, apparel = None, background = None):
    550         context = CharacterSceneContext(charID, dna=dna, apparel=apparel, background=background)
    551         return self.LoadScene(context)
    552 
    553     def PreviewShip(self, typeID, controller = None, skin_state = None, scenePath = None, subsystems = None, **kwargs):
    554         skin = skin_state
    555         if skin_state:
    556             if skin_state.skin_type == ShipSkinType.FIRST_PARTY_SKIN:
    557                 skin = sm.GetService('cosmeticsSvc').GetFirstPartySkinObject(licenseeID=skin_state.character_id, skinID=skin_state.skin_data.skin_id)
    558             elif skin_state.skin_type == ShipSkinType.THIRD_PARTY_SKIN:
    559                 if skin_state.character_id == session.charid:
    560                     try:
    561                         skin_license = get_ship_skin_license_svc().get_license(license_id=skin_state.skin_data.skin_id, owner_character_id=skin_state.character_id)
    562                         if skin_license:
    563                             skin = ThirdPartySkinWrapper(skin_license)
    564                     except (GenericException, TimeoutException):
    565                         message_player(GetByLabel('UI/Common/CannotConnectToServer'))
    566 
    567         if controller:
    568             controller.onChange.disconnect(self.UpdateShipEntity)
    569             if controller.typeID != typeID:
    570                 controller.typeID = typeID
    571             if skin:
    572                 for s in controller.skins:
    573                     if s == skin:
    574                         controller.PickSkinAndMoveToIt(s, deselectIfSame=False)
    575                         break
    576 
    577             self.skinController = controller
    578             controller.onChange.connect(self.UpdateShipEntity)
    579         self.scenePath = scenePath
    580         return self.PreviewShipEntity(typeID, skin=skin_state, scenePath=scenePath, subsystems=subsystems)
    581 
    582     def PreviewSkin(self, typeID, controller = None, scenePath = None, subsystems = None, otherTypeID = None, **kwargs):
    583         skin = sm.GetService('cosmeticsSvc').GetSkinByLicenseType(typeID)
    584         entityTypeID = otherTypeID if otherTypeID and otherTypeID in skin.types else skin.types[0]
    585         if controller:
    586             controller.onChange.disconnect(self.UpdateSkinnedEntity)
    587             if controller.typeID != entityTypeID:
    588                 controller.typeID = entityTypeID
    589             for s in controller.skins:
    590                 if s.skinID == skin.skinID:
    591                     controller.PickSkinAndMoveToIt(s, deselectIfSame=False)
    592                     break
    593 
    594             self.skinController = controller
    595             controller.onChange.connect(self.UpdateSkinnedEntity)
    596         self.scenePath = scenePath
    597         return self.PreviewShipEntity(entityTypeID, skin=skin, scenePath=scenePath, subsystems=subsystems)
    598 
    599     def UpdateSkinnedEntity(self):
    600         typeID = self.skinController.typeID
    601         if IsShipType(typeID):
    602             self.UpdateShipEntity()
    603             return
    604         materialSetID = None if self.skinController.previewed is None else self.skinController.previewed.materialSetID
    605         uthread.new(self.PreviewSpaceEntity, typeID, materialSetID=materialSetID, scenePath=self.scenePath, subsystems=getattr(self.context, 'subsystems', None), resetZoom=False)
    606 
    607     def UpdateShipEntity(self):
    608         uthread.new(self.PreviewShipEntity, self.skinController.typeID, skin=self.skinController.previewed, scenePath=self.scenePath, subsystems=getattr(self.context, 'subsystems', None), resetZoom=False)
    609 
    610     def PreviewSpaceEntity(self, typeID, itemID = None, materialSetID = None, subsystems = None, scenePath = None, resetZoom = True):
    611         context = SpaceEntitySceneContext(typeID, itemID=itemID, materialSetID=materialSetID, subsystems=subsystems, scenePath=scenePath, resetZoom=resetZoom)
    612         return self.LoadScene(context)
    613 
    614     def PreviewShipEntity(self, typeID, skin = None, scenePath = None, subsystems = None, resetZoom = True):
    615         context = ShipEntitySceneContext(typeID, skin=skin, subsystems=subsystems, scenePath=scenePath, resetZoom=resetZoom)
    616         return self.LoadScene(context)
    617 
    618     def PreviewSofDna(self, dna, dirt = None, scenePath = None):
    619         context = SofDnaSceneContext(dna, dirt=dirt, scenePath=scenePath)
    620         return self.LoadScene(context)
    621 
    622     def PreviewTurret(self, typeID, scenePath = None):
    623         context = TurretSceneContext(typeID, scenePath=scenePath)
    624         return self.LoadScene(context)
    625 
    626     def LoadScene(self, context, force = False):
    627         if context == self.context and not force:
    628             return False
    629         success = True
    630         try:
    631             self._OnStartLoading()
    632             self._Cleanup()
    633             self.context = context
    634             self.context.LoadScene(self.sceneContainer)
    635             self.UpdateViewPort()
    636         except Exception:
    637             if not self.destroyed:
    638                 log.LogException('Exception raised while loading preview for {context}'.format(context=str(context)))
    639                 sys.exc_clear()
    640                 success = False
    641 
    642         self._OnStopLoading(success)
    643         return success
    644 
    645     def Reload(self):
    646         if self.context:
    647             self.LoadScene(self.context, force=True)
    648 
    649     def _Cleanup(self):
    650         try:
    651             if self.context:
    652                 self.context.Cleanup()
    653                 self.context = None
    654         except Exception:
    655             log.LogException('Exception raised during preview container cleanup', severity=log.INFO)
    656             sys.exc_clear()
    657 
    658     def AnimEntry(self, yaw0 = 1.1 * math.pi, pitch0 = 0.0, yaw1 = 1.25 * math.pi, pitch1 = 0.3, duration = 2.0):
    659         self.sceneContainer.AnimEntry(yaw0, pitch0, yaw1, pitch1, duration)
    660 
    661     def UpdateViewPort(self):
    662         if self.sceneContainer:
    663             self.sceneContainer.UpdateViewPort()
    664 
    665     def OnSetDevice(self):
    666         uthread.new(self.Reload)
    667 
    668     def OnGraphicSettingsChanged(self, changes):
    669         if self.context and any((setting in self.context.relevantSettings for setting in changes)):
    670             uthread.new(self.Reload)
    671 
    672 
    673 class SceneContext(object):
    674     relevantSettings = []
    675 
    676     def LoadScene(self, sceneContainer):
    677         raise NotImplementedError('SceneContexts must override the LoadScene method')
    678 
    679     def Cleanup(self):
    680         pass
    681 
    682 
    683 class ApparelSceneContext(SceneContext):
    684     relevantSettings = [gfxsettings.GFX_CHAR_TEXTURE_QUALITY]
    685 
    686     def __init__(self, typeID, gender = None, background = None):
    687         if evetypes.GetCategoryID(typeID) != invconst.categoryApparel:
    688             raise InvalidPreviewType('%s (%s) is not an apparel item' % (evetypes.GetName(typeID), typeID))
    689         self.typeID = typeID
    690         self.gender = gender
    691         self.background = background
    692         self.mannequin = None
    693 
    694     def __eq__(self, other):
    695         return isinstance(other, ApparelSceneContext) and self.typeID == other.typeID and self.gender == other.gender and self.background == other.background
    696 
    697     def LoadScene(self, sceneContainer):
    698         sceneContainer.PrepareInteriorScene(backgroundImage=self.background, useShadows=True)
    699         apparel = GetPaperDollResource(self.typeID, gender=self.gender)
    700         if apparel is None:
    701             raise InvalidPreviewType('%s (%s) does not have an associated paper doll resource' % (evetypes.GetName(self.typeID), self.typeID))
    702         factory = Factory()
    703         mannequin = PaperDollCharacter(factory)
    704         self.mannequin = mannequin
    705         dollGender = GenderIDToPaperDollGender(apparel.resGender)
    706         mannequin.doll = Doll('mannequin', gender=dollGender)
    707         mannequin.doll.Load(MANNEQUIN_RES_BY_GENDER[dollGender], factory)
    708         mannequin.WaitForUpdate()
    709         textureQuality = gfxsettings.Get(gfxsettings.GFX_CHAR_TEXTURE_QUALITY)
    710         resolution = ccConst.TEXTURE_RESOLUTIONS[textureQuality]
    711         mannequin.doll.textureResolution = resolution
    712         mannequin.doll.overrideLod = LOD_SKIN
    713         mannequin.Spawn(sceneContainer.scene)
    714         mannequin.WaitForUpdate()
    715         with CaptureDollMeshChanges(mannequin.doll) as meshes:
    716             modifier = mannequin.doll.SetItemType(factory, apparel.resPath)
    717             if 'tattoo/head' in apparel.resPath.lower():
    718                 modifier.SetColorVariation('default_a')
    719             typeData = factory.GetItemType(apparel.resPath, gender=dollGender)
    720             apparelCategory = sm.GetService('character').GetCategoryFromResPath(typeData[0])
    721             coveringCategories = PAPERDOLL_CATEGORIES_COVERING.get(apparelCategory, [])
    722             for category in coveringCategories:
    723                 mannequin.doll.buildDataManager.RemoveMeshContainingModifiers(category)
    724 
    725             mannequin.Update()
    726             mannequin.WaitForUpdate()
    727         newMeshes = set(filter(lambda m: m not in meshes.before, meshes.after))
    728         assetGroupID = evetypes.GetGroupID(self.typeID)
    729         if apparelCategory == 'makeup/bodyaugmentations':
    730             assetGroupID = invconst.groupApparelOuter
    731         if apparelCategory == 'tattoo/head':
    732             assetGroupID = invconst.groupApparelAugmentations
    733         meshNameCheck = lambda mesh: any(map(lambda name: mesh.name.startswith(name), MESH_NAMES_BY_GROUPID[assetGroupID]))
    734         groupMeshes = set(filter(meshNameCheck, meshes.after))
    735         boundingBoxes = map(lambda m: m.geometry.GetBoundingBox(0), newMeshes | groupMeshes)
    736         if len(boundingBoxes) == 0:
    737             aabb = mannequin.visualModel.GetBoundingBoxInLocalSpace()
    738         else:
    739             aabb = reduce(MergeBoundingBoxes, boundingBoxes)
    740         if dollGender == GENDER.FEMALE:
    741             animationRes = 'res:/Animation/PortraitCreator/Female/CharacterCreation/Mannequin.gr2'
    742         else:
    743             animationRes = 'res:/Animation/PortraitCreator/Male/CharacterCreation/Mannequin.gr2'
    744         animationUpdater = trinity.Tr2GrannyAnimation()
    745         animationUpdater.resPath = animationRes
    746         if animationUpdater is not None:
    747             mannequin.avatar.animationUpdater = animationUpdater
    748         floorShadow = trinity.Load(ccConst.CUSTOMIZATION_FLOOR)
    749         sceneContainer.scene.dynamics.append(floorShadow)
    750         SetupInteriourCamera(sceneContainer, aabb)
    751 
    752     def Cleanup(self):
    753         self.mannequin = None
    754 
    755 
    756 class CharacterSceneContext(SceneContext):
    757     relevantSettings = [gfxsettings.GFX_CHAR_TEXTURE_QUALITY, gfxsettings.UI_NCC_GREEN_SCREEN, gfxsettings.GFX_CHAR_FAST_CHARACTER_CREATION]
    758 
    759     def __init__(self, charID, dna = None, apparel = None, background = None):
    760         dna = dna or sm.RemoteSvc('paperDollServer').GetPaperDollData(charID)
    761         self.charID = charID
    762         self.dna = dna
    763         self.apparel = apparel or []
    764         self.background = background
    765         self.boundingBox = None
    766         self.character = None
    767 
    768     def __eq__(self, other):
    769         return isinstance(other, CharacterSceneContext) and self.charID == other.charID and self.dna == other.dna and self.apparel == other.apparel and self.background == other.background
    770 
    771     def LoadScene(self, sceneContainer):
    772         if idCheckers.IsNPCCharacter(self.charID):
    773             charInfo = get_npc_character(self.charID)
    774         else:
    775             charInfo = sm.RemoteSvc('charMgr').GetPublicInfo(self.charID)
    776         background = self.GetBackground()
    777         sceneFile = self.GetSceneFile(charInfo)
    778         sceneContainer.PrepareInteriorScene(useShadows=True, backgroundImage=background, sceneFile=sceneFile)
    779         if charInfo.gender is None:
    780             owner = cfg.eveowners.Get(self.charID)
    781             raise RuntimeError('{0.name} ({0.charID}) does not have a defined gender'.format(owner))
    782         scene = sceneContainer.scene
    783         self.character = self.AddCharacterToScene(charInfo, scene)
    784         self.PrepareCharacterAnimation(self.character.avatar, charInfo.gender)
    785         try:
    786             self.boundingBox = self.character.visualModel.GetBoundingBoxInLocalSpace()
    787         except RuntimeError:
    788             self.boundingBox = ((-0.5, -0.5, 0), (0.5, 0.5, 1.5))
    789 
    790         useFastShader = gfxsettings.Get(gfxsettings.GFX_CHAR_FAST_CHARACTER_CREATION)
    791         if useFastShader:
    792             sceneContainer.scene.ambientColor = (0.25, 0.25, 0.25)
    793         if not gfxsettings.Get(gfxsettings.UI_NCC_GREEN_SCREEN):
    794             floor = trinity.Load(ccConst.CUSTOMIZATION_FLOOR)
    795             sceneContainer.scene.dynamics.append(floor)
    796         self.SetupCamera(sceneContainer)
    797 
    798     def AddCharacterToScene(self, charInfo, scene):
    799         character = sm.GetService('character').AddCharacterToScene(charID=self.charID, scene=scene, gender=GenderIDToPaperDollGender(charInfo.gender), bloodlineID=charInfo.bloodlineID, raceID=None, dna=self.dna, lod=LOD_SKIN, updateDoll=False)
    800         textureQuality = gfxsettings.Get(gfxsettings.GFX_CHAR_TEXTURE_QUALITY)
    801         textureResolution = ccConst.DOLL_VIEWER_TEXTURE_RESOLUTIONS[textureQuality]
    802         character.doll.textureResolution = textureResolution
    803         useFastShader = gfxsettings.Get(gfxsettings.GFX_CHAR_FAST_CHARACTER_CREATION)
    804         character.doll.useFastShader = useFastShader
    805         for typeID in self.apparel:
    806             apparel = GetPaperDollResource(typeID, gender=charInfo.gender)
    807             if apparel is None:
    808                 log.LogError('Unable to preview %s (%s) since it has no associated resource' % (evetypes.GetName(typeID), typeID))
    809                 continue
    810             sm.GetService('character').ApplyTypeToDoll(self.charID, apparel.resPath, doUpdate=False)
    811 
    812         character.Update()
    813         character.WaitForUpdate()
    814         return character
    815 
    816     def PrepareCharacterAnimation(self, avatar, gender):
    817         pass
    818 
    819     def GetBackground(self):
    820         background = self.background
    821         if gfxsettings.Get(gfxsettings.UI_NCC_GREEN_SCREEN):
    822             background = 'res:/UI/Texture/CharacterCreation/backdrops/Background_1001.dds'
    823         return background
    824 
    825     def GetSceneFile(self, charInfo):
    826         return 'res:/Graphics/Interior/characterCreation/Preview.red'
    827 
    828     def SetupCamera(self, sceneContainer):
    829         SetupInteriourCamera(sceneContainer, self.boundingBox)
    830 
    831     def Cleanup(self):
    832         sm.GetService('character').RemoveCharacter(self.charID)
    833 
    834 
    835 def _UpdateLighting(scene, camera, ship, alive):
    836     scene.nebulaIntensity = 1
    837     scene.sunDiffuseColor = (3, 3, 3)
    838     while alive:
    839         direction = geo2.Vec3Normalize(geo2.Vec3Subtract(camera.atPosition, camera.eyePosition))
    840         scene.sunDirection = direction
    841         uthread2.Yield()
    842 
    843 
    844 class SpaceEntitySceneContext(SceneContext):
    845 
    846     def __init__(self, typeID, itemID = None, materialSetID = None, subsystems = None, scenePath = None, resetZoom = True):
    847         self.typeID = typeID
    848         self.itemID = itemID
    849         self.materialSetID = materialSetID
    850         self.subsystems = subsystems
    851         self.alive = []
    852         self.resetZoom = resetZoom
    853         if scenePath is None:
    854             hullTypeIDList = self.subsystems
    855             if subsystems is not None:
    856                 hullTypeIDList = self.subsystems.values()
    857             modelDNA = gfxutils.BuildSOFDNAFromTypeID(self.typeID, materialSetID=self.materialSetID, multiHullTypeIDList=hullTypeIDList)
    858             if modelDNA is not None:
    859                 raceName = self.GetRaceFromDNAString(modelDNA)
    860                 scenePath = eveSpaceObject.GetPreviewScenePathByRaceName(raceName)
    861             else:
    862                 scenePath = eveSpaceObject.GetPreviewScenePathByRaceName('')
    863         self.scenePath = scenePath
    864 
    865     def __eq__(self, other):
    866         return isinstance(other, SpaceEntitySceneContext) and self.typeID == other.typeID and self.itemID == other.itemID and self.materialSetID == other.materialSetID and self.subsystems == other.subsystems and self.scenePath == other.scenePath
    867 
    868     @staticmethod
    869     def GetRaceFromDNAString(dnaString):
    870         parts = dnaString.split(':')
    871         return parts[2]
    872 
    873     def LoadScene(self, sceneContainer):
    874         sceneContainer.PrepareSpaceScene(scenePath=self.scenePath, resetZoom=self.resetZoom)
    875         resFile = inventorycommon.typeHelpers.GetGraphicFile(self.typeID)
    876         modelDNA = None
    877         if evetypes.GetCategoryID(self.typeID) == invconst.categoryStation and self.itemID:
    878             stations = cfg.mapSolarSystemContentCache.npcStations
    879             npcStation = stations.get(self.itemID, None)
    880             if npcStation:
    881                 graphicID = npcStation.graphicID
    882                 modelDNA = gfxutils.BuildSOFDNAFromGraphicID(graphicID)
    883         subsystems = None
    884         if self.subsystems is not None and len(self.subsystems) > 0:
    885             subsystems = self.subsystems.values()
    886         if modelDNA is None:
    887             modelDNA = gfxutils.BuildSOFDNAFromTypeID(self.typeID, materialSetID=self.materialSetID, multiHullTypeIDList=subsystems)
    888         if modelDNA is not None:
    889             spaceObjectFactory = GetSofService().spaceObjectFactory
    890             model = spaceObjectFactory.BuildFromDNA(modelDNA)
    891         else:
    892             model = trinity.Load(resFile)
    893         if model is None:
    894             raise InvalidPreviewType('%s (%s) failed to load associated model' % (evetypes.GetName(self.typeID), self.typeID))
    895         if getattr(model, 'boosters', None) is not None:
    896             model.boosters = None
    897         if getattr(model, 'modelRotationCurve', None) is not None:
    898             model.modelRotationCurve = None
    899         if getattr(model, 'modelTranslationCurve', None) is not None:
    900             model.modelTranslationCurve = None
    901         SetupAnimations(model)
    902         trinity.WaitForResourceLoads()
    903         model.FreezeHighDetailMesh()
    904         sceneContainer.AddToScene(model)
    905         SetupSpaceCamera(sceneContainer, model, resetZoom=self.resetZoom)
    906         self.alive = [True]
    907         uthread2.StartTasklet(_UpdateLighting, sceneContainer.scene, sceneContainer.camera, model, self.alive)
    908 
    909     def Cleanup(self):
    910         del self.alive[:]
    911 
    912 
    913 class ShipEntitySceneContext(SceneContext):
    914 
    915     def __init__(self, typeID, skin = None, subsystems = None, scenePath = None, resetZoom = True):
    916         self.typeID = typeID
    917         self.skin = skin
    918         self.subsystems = subsystems
    919         self.alive = []
    920         self.resetZoom = resetZoom
    921         self.scenePath = scenePath or self._GetDefaultScenePath()
    922 
    923     def __eq__(self, other):
    924         return isinstance(other, SpaceEntitySceneContext) and self.typeID == other.typeID and self.skin == other.skin and self.subsystems == other.subsystems and self.scenePath == other.scenePath
    925 
    926     def _GetDefaultScenePath(self):
    927         hullTypeIDList = self.subsystems.values() if self.subsystems else []
    928         modelDNA = gfxutils.BuildSOFDNAFromTypeID(self.typeID, multiHullTypeIDList=hullTypeIDList)
    929         if modelDNA is not None:
    930             raceName = self.GetRaceFromDNAString(modelDNA)
    931             scenePath = eveSpaceObject.GetPreviewScenePathByRaceName(raceName)
    932         else:
    933             scenePath = eveSpaceObject.GetPreviewScenePathByRaceName('')
    934         return scenePath
    935 
    936     @staticmethod
    937     def GetRaceFromDNAString(dnaString):
    938         parts = dnaString.split(':')
    939         return parts[2]
    940 
    941     def LoadScene(self, sceneContainer):
    942         sceneContainer.PrepareSpaceScene(scenePath=self.scenePath, resetZoom=self.resetZoom)
    943         subsystems = self.subsystems.values() if self.subsystems else None
    944         model = GetSkinnedShipModel(skin=self.skin, typeID=self.typeID, multiHullTypeIDList=subsystems)
    945         if model is None:
    946             resFile = inventorycommon.typeHelpers.GetGraphicFile(self.typeID)
    947             model = trinity.Load(resFile)
    948         if model is None:
    949             raise InvalidPreviewType('%s (%s) failed to load associated model' % (evetypes.GetName(self.typeID), self.typeID))
    950         if getattr(model, 'boosters', None) is not None:
    951             model.boosters = None
    952         if getattr(model, 'modelRotationCurve', None) is not None:
    953             model.modelRotationCurve = None
    954         if getattr(model, 'modelTranslationCurve', None) is not None:
    955             model.modelTranslationCurve = None
    956         SetupAnimations(model)
    957         trinity.WaitForResourceLoads()
    958         model.FreezeHighDetailMesh()
    959         sceneContainer.AddToScene(model)
    960         SetupSpaceCamera(sceneContainer, model, resetZoom=self.resetZoom)
    961         self.alive = [True]
    962         uthread2.StartTasklet(_UpdateLighting, sceneContainer.scene, sceneContainer.camera, model, self.alive)
    963 
    964     def Cleanup(self):
    965         del self.alive[:]
    966 
    967 
    968 class SofDnaSceneContext(SceneContext):
    969 
    970     def __init__(self, dna, dirt = None, scenePath = None):
    971         if scenePath is None:
    972             scenePath = GetGraphicFile(20413)
    973         self.dna = dna
    974         self.dirt = dirt
    975         self.scenePath = scenePath
    976         self.model = None
    977 
    978     def __eq__(self, other):
    979         return isinstance(other, SofDnaSceneContext) and self.dna == other.dna and self.scenePath == other.scenePath and self.dirt == other.dirt
    980 
    981     def LoadScene(self, sceneContainer):
    982         try:
    983             sceneContainer.PrepareSpaceScene(scenePath=self.scenePath)
    984         except RuntimeError:
    985             return
    986 
    987         spaceObjectFactory = GetSofService().spaceObjectFactory
    988         model = spaceObjectFactory.BuildFromDNA(self.dna)
    989         self.model = model
    990         if model is None:
    991             raise InvalidPreviewType('failed to load associated model: ' + self.dna)
    992         if self.dirt is not None:
    993             model.dirtLevel = self.dirt
    994         sceneContainer.AddToScene(model)
    995         SetupSpaceCamera(sceneContainer, model)
    996 
    997     def GetModel(self):
    998         return self.model
    999 
   1000 
   1001 class TurretSceneContext(SceneContext):
   1002 
   1003     def __init__(self, typeID, scenePath = None):
   1004         if evetypes.GetGroupID(typeID) not in invconst.turretModuleGroups:
   1005             raise InvalidPreviewType('%s (%s) is not a turret module' % (evetypes.GetName(typeID), typeID))
   1006         self.typeID = typeID
   1007         self.scenePath = scenePath or 'res:/dx9/scene/fitting/previewTurrets.red'
   1008 
   1009     def __eq__(self, other):
   1010         return isinstance(other, TurretSceneContext) and self.typeID == other.typeID and self.scenePath == other.scenePath
   1011 
   1012     def LoadScene(self, sceneContainer):
   1013         sceneContainer.PrepareSpaceScene(maxPitch=math.pi / 2 - 0.1, scenePath=self.scenePath)
   1014         model = trinity.Load('res:/dx9/model/ship/IconPreview/PreviewTurretShip.red')
   1015         graphicID = evetypes.GetGraphicID(self.typeID)
   1016         turretSet = TurretSet.FitTurret(model, self.typeID, 1, GetSofFactionName(graphicID), checkSettings=False)
   1017         if turretSet is None:
   1018             raise RuntimeError('Failed to load preview for %s (%s)' % (evetypes.GetName(self.typeID), self.typeID))
   1019         boundingSphere = turretSet.turretSets[0].boundingSphere
   1020         model.boundingSphereRadius = boundingSphere[3]
   1021         model.boundingSphereCenter = boundingSphere[:3]
   1022         if model.boundingSphereCenter[1] < 2.0:
   1023             model.boundingSphereCenter = (boundingSphere[0], 2.0, boundingSphere[2])
   1024         for turret in turretSet.turretSets:
   1025             if 'moonharvester' not in turret.locatorName:
   1026                 turret.bottomClipHeight = 0.0
   1027             else:
   1028                 turret.display = True
   1029             turret.FreezeHighDetailLOD()
   1030             turret.ForceStateDeactive()
   1031             turret.EnterStateIdle()
   1032 
   1033         sceneContainer.AddToScene(model)
   1034         SetupSpaceCamera(sceneContainer, model)
   1035 
   1036 
   1037 def SetupSpaceCamera(sceneContainer, model, resetZoom = True):
   1038     sceneContainer.verticalPanLimits = None
   1039     lookatOffset = GetModelLookAtOffset(model)
   1040     camera = sceneContainer.camera
   1041     camera.SetAtPosition(lookatOffset)
   1042     alpha = sceneContainer.fov / 2.0
   1043     radius = model.GetBoundingSphereRadius() + geo2.Vec3Length(lookatOffset)
   1044     maxZoom = min(sceneContainer.backClip - radius, max(radius * (1 / math.tan(alpha)) * 2, 1.0))
   1045     minZoom = radius + sceneContainer.frontClip
   1046     sceneContainer.SetMinMaxZoom(minZoom, maxZoom)
   1047     if resetZoom:
   1048         sceneContainer.SetZoom(0.5)
   1049 
   1050 
   1051 def SetupInteriourCamera(sceneContainer, boundingBox):
   1052     p0, p1 = geo2.Vector(boundingBox[0]), geo2.Vector(boundingBox[1])
   1053     center = 0.5 * (p1 - p0) + p0
   1054     camera = sceneContainer.camera
   1055     camera.SetAtPosition(center)
   1056     sceneContainer.verticalPanLimits = (p0.y, p1.y)
   1057     rad = max(geo2.Vec3Length(p0 - p1), 0.3)
   1058     alpha = sceneContainer.fov * 1.5 / 2.0
   1059     maxZoom = min(rad * (1 / math.tan(alpha)), 9.0)
   1060     minZoom = rad + sceneContainer.frontClip
   1061     sceneContainer.SetMinMaxZoom(minZoom, maxZoom)
   1062     camera.SetZoomLinear(0.6)
   1063     camera.kMinPitch = 0.0
   1064     camera.kMaxPitch = math.pi / 2.0
   1065     camera.kOrbitSpeed = 30.0
   1066     camera.farClip = 100.0
   1067 
   1068 
   1069 class CaptureDollMeshChanges(object):
   1070 
   1071     def __init__(self, doll):
   1072         self.doll = doll
   1073 
   1074     def __enter__(self):
   1075         self.before = self.doll.buildDataManager.GetMeshes(includeClothMeshes=True)
   1076         return self
   1077 
   1078     def __exit__(self, type, value, traceback):
   1079         self.after = self.doll.buildDataManager.GetMeshes(includeClothMeshes=True)
   1080 
   1081 
   1082 def MergeBoundingBoxes(a, b):
   1083     merged = ((min(a[0][0], b[0][0]), min(a[0][1], b[0][1]), min(a[0][2], b[0][2])), (max(a[1][0], b[1][0]), max(a[1][1], b[1][1]), max(a[1][2], b[1][2])))
   1084     return merged
   1085 
   1086 
   1087 SCHEME = 'preview'
   1088 
   1089 def register_link_handlers(registry):
   1090     registry.register(SCHEME, handle_preview_link, hint='UI/Preview/Preview')
   1091 
   1092 
   1093 def handle_preview_link(url):
   1094     type_id = parse_preview_url(url)
   1095     sm.GetService('preview').PreviewType(type_id)
   1096 
   1097 
   1098 def parse_preview_url(url):
   1099     return int(url[url.index(':') + 1:])