icongenerator.py (9322B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\iconrendering2\icongenerator.py 2 from __future__ import print_function 3 import os 4 import osutils 5 import tempfile 6 import subprocess 7 import shutil 8 import imgdiff 9 import blue 10 from iconrendering2.factory.factory import IconRendererFactory 11 from iconrendering2.const import IconStyle, InputType, Language, DEFAULT_ICON_DIFF_TOLERANCE, ALL_METAGROUPS 12 try: 13 from platformtools.compatibility.exposure import perforceutils 14 try: 15 perforceutils.get_connection() 16 p4enabled = True 17 except perforceutils.P4Exception: 18 p4enabled = False 19 20 except ImportError: 21 perforceutils = None 22 p4enabled = False 23 24 class Options(object): 25 NONE = 0 26 USE_TMP_FOLDER = 1 27 CHECKOUT = 2 28 CLEAN_TMP_FOLDER = 4 29 VIEW_OUTPUT = 8 30 ONLY_USE_EXISTING_BASE_ICONS = 16 31 DONT_RENDER_METAGROUP_OVERLAYS = 32 32 33 34 class _JobInfo(object): 35 36 def __init__(self, options, outputFolder, tmpOutputFolder, renderers, errors): 37 self.options = options 38 self.outputFolder = outputFolder 39 self.tmpOutputFolder = tmpOutputFolder 40 self.renderers = renderers 41 self.errors = errors 42 43 44 class IconGenerator(object): 45 46 def __init__(self, resourceMapper = None, sofFactory = None, changelist = None, language = Language.ENGLISH, preRenderCallback = None, postRenderCallback = None, options = Options.NONE): 47 self._resourceMapper = resourceMapper 48 self._sofFactory = sofFactory 49 self._changelist = changelist 50 self._language = language 51 self._preRenderCallback = preRenderCallback 52 self._postRenderCallback = postRenderCallback 53 self._factory = IconRendererFactory(self._resourceMapper, self._sofFactory) 54 self._options = options 55 self._options &= ~Options.VIEW_OUTPUT 56 self._viewOutput = options & Options.VIEW_OUTPUT 57 self._openedFolders = [] 58 self._scheduledJobs = {} 59 60 def Generate(self, inputType, input, outputFolder = None, metadata = None, desiredStyles = IconStyle.ALL, desiredMetagroups = ALL_METAGROUPS, desiredSizes = None): 61 errors = [] 62 try: 63 job = self._CreateJob(inputType, input, outputFolder, metadata, desiredStyles, desiredMetagroups, desiredSizes) 64 if job: 65 errors.extend(job.errors) 66 errors.extend(self._Run(input, [job])) 67 except Exception: 68 import traceback 69 errors.append(''.join(traceback.format_exc())) 70 71 return errors 72 73 def Schedule(self, inputType, input, outputFolder = None, metadata = None, desiredStyles = IconStyle.ALL, desiredMetagroups = ALL_METAGROUPS, desiredSizes = None): 74 job = self._CreateJob(inputType, input, outputFolder, metadata, desiredStyles, desiredMetagroups, desiredSizes) 75 if job: 76 self._Schedule(input, job) 77 78 def Unschedule(self, input): 79 if input in self._scheduledJobs: 80 self._scheduledJobs.pop(input) 81 82 def RenderScheduled(self): 83 errors = [] 84 for input, jobs in self._scheduledJobs.iteritems(): 85 for job in jobs: 86 errors.extend(job.errors) 87 88 errors.extend(self._Run(input, jobs)) 89 90 self._scheduledJobs = {} 91 return errors 92 93 def CancelScheduled(self): 94 self._scheduledJobs = {} 95 96 def _Schedule(self, input, job): 97 if input not in self._scheduledJobs: 98 self._scheduledJobs[input] = [] 99 self._scheduledJobs[input].append(job) 100 101 def _Run(self, input, jobs): 102 outputFolders = set() 103 errors = [] 104 105 def _callback(outputList): 106 if self._postRenderCallback: 107 self._postRenderCallback(input, outputList) 108 109 for j in jobs: 110 outputInfos = [] 111 for r in j.renderers: 112 renderErrors = r.Render() 113 outputInfos.extend(r.GetOutputList()) 114 for e in renderErrors: 115 errors.append((input, e[0], e[1])) 116 117 outputFiles = [ info.outputPath for info in outputInfos ] 118 for path in outputFiles: 119 print('Generated icon: {}'.format(path)) 120 121 if _callback: 122 _callback(outputInfos) 123 if j.options & Options.USE_TMP_FOLDER: 124 errors.extend(_CopyToFinalFolder(j.outputFolder, outputFiles, self._changelist if p4enabled else None)) 125 _CleanTmpFolder(j.tmpOutputFolder, j.options) 126 outputFolders.add(j.outputFolder) 127 128 if self._viewOutput: 129 for output in outputFolders: 130 if output not in self._openedFolders: 131 self._openedFolders.append(output) 132 resolvedPath = blue.paths.ResolvePath(output) 133 subprocess.Popen('explorer %s' % resolvedPath.replace('/', '\\')) 134 135 return errors 136 137 def _CreateJob(self, inputType, input, outputFolder, metadata, desiredStyles, desiredMetagroups, desiredSizes = None): 138 if not desiredSizes: 139 desiredSizes = [64, 128, 512] 140 if metadata and metadata.get('skip', False): 141 print('Skipping icons, as indicated in the metadata.') 142 return None 143 if not self._resourceMapper and inputType in [InputType.RED_FILE, InputType.GRAPHIC_ID]: 144 print("No Resource Mapper defined, can't generate icons for %s (%s)" % (input, inputType)) 145 return None 146 if not self._sofFactory and inputType in [InputType.RED_FILE, InputType.GRAPHIC_ID, InputType.DNA]: 147 print("No SOF Factory defined, can't generate icons for %s (%s)" % (input, inputType)) 148 return None 149 options = _ReviewOptions(self._options) 150 outputFolder, tmpOutputFolder = _GetCorrectFolders(outputFolder, options) 151 onlyUseExistingBaseIcons = Options.ONLY_USE_EXISTING_BASE_ICONS & self._options 152 renderMetagroupOverlays = not Options.DONT_RENDER_METAGROUP_OVERLAYS & self._options 153 renderers, errors = self._factory.CreateRenderers(inputType, input, tmpOutputFolder, desiredStyles, desiredMetagroups, desiredSizes, onlyUseExistingBaseIcons, renderMetagroupOverlays, metadata, self._language) 154 if self._preRenderCallback: 155 outputInfos = [] 156 for r in renderers: 157 outputInfos.extend(r.GetOutputList()) 158 159 self._preRenderCallback(input, outputInfos) 160 return _JobInfo(options, outputFolder, tmpOutputFolder, renderers, errors) 161 162 163 def CompareIcons(iconA, iconB, tolerance = DEFAULT_ICON_DIFF_TOLERANCE): 164 diffResult = imgdiff.ImgDiff(iconA, iconB, normal=False, alpha=False) 165 error = diffResult['Color']['MeanAbsoluteError'] 166 return error <= tolerance 167 168 169 def _ReviewOptions(options): 170 if options & Options.CHECKOUT: 171 options = options | Options.USE_TMP_FOLDER 172 return options 173 174 175 def _GetCorrectFolders(outputFolder, options): 176 if options & Options.USE_TMP_FOLDER: 177 tmpOutputFolder = os.path.join(tempfile.mkdtemp('Icons')) 178 if outputFolder is None: 179 outputFolder = tmpOutputFolder 180 else: 181 if outputFolder is None: 182 print() 183 return (None, None, ["No output folder or temp folder defined, can't generate icons."]) 184 tmpOutputFolder = outputFolder 185 return (outputFolder, tmpOutputFolder) 186 187 188 def _ProcessCopyErrors(failed): 189 errors = [] 190 for src, dst, excstring in failed: 191 if os.path.exists(dst): 192 errors.append('Failed to copy new icon to target directory:\n icon: {}\n target: {}\n{}'.format(src, dst, excstring)) 193 else: 194 errors.append('Failed to overwrite existing icon with new generated version:\n new icon: {}\n old icon: {}\n{}'.format(src, dst, excstring)) 195 196 return errors 197 198 199 def _CreateGenerationFailedErrors(copyOps, outputFiles): 200 if len(copyOps) != len(outputFiles): 201 return [ "Failed to finalize icon, icon wasn't generated: {}".format(x) for x in outputFiles if not os.path.exists(x) ] 202 else: 203 return [] 204 205 206 def _CopyToFinalFolder(outputFolder, outputFiles, changelist): 207 resolvedOutputFolder = blue.paths.ResolvePath(outputFolder) 208 copyOps = [ (os.path.abspath(x), os.path.abspath(os.path.join(resolvedOutputFolder, os.path.basename(x)))) for x in outputFiles if os.path.exists(x) ] 209 errors = _CreateGenerationFailedErrors(copyOps, outputFiles) 210 211 def copyFiles(): 212 failed = [] 213 for src, dst in copyOps: 214 if dst != src and os.path.exists(src): 215 try: 216 if not os.path.exists(dst): 217 open(dst, 'a').close() 218 shutil.copy(src, dst) 219 except (IOError, OSError): 220 import traceback 221 failed += (src, dst, ''.join(traceback.format_exc())) 222 223 return failed 224 225 if changelist is not None and perforceutils is not None: 226 with perforceutils.checkout([ dst for _, dst in copyOps ], changelist): 227 failed = copyFiles() 228 else: 229 failed = copyFiles() 230 errors.extend(_ProcessCopyErrors(failed)) 231 return errors 232 233 234 def _CleanTmpFolder(tmpOutputFolder, options): 235 if options & Options.CLEAN_TMP_FOLDER and options & Options.USE_TMP_FOLDER: 236 osutils.SafeRmTree(tmpOutputFolder)