|
| 1 | +''' |
| 2 | +Package TOX for Release Class |
| 3 | +Authors | matthew ragan |
| 4 | +matthewragan.com |
| 5 | +''' |
| 6 | + |
| 7 | +import webbrowser |
| 8 | +import json |
| 9 | + |
| 10 | +popupMsg = '''Your file is saved as | {name} |
| 11 | +Save Location | {location}''' |
| 12 | + |
| 13 | + |
| 14 | +class PackageTOX: |
| 15 | + ''' |
| 16 | + This class is designed to operate as a general helper extension. |
| 17 | +
|
| 18 | + When sharing a TOX it's often difficult to ensure that it's correctly |
| 19 | + packaged in a uniform and consistent manner that will ensure it's immediately usable by a |
| 20 | + third party. This TOX aims to simplify some of that process by automating the prep and |
| 21 | + packaging of a TOX in a consistent and reliable manner. |
| 22 | + --------------- |
| 23 | +
|
| 24 | + ''' |
| 25 | + |
| 26 | + def __init__(self, ownerOp): |
| 27 | + ''' |
| 28 | + ''' |
| 29 | + self.OwnerOp = ownerOp |
| 30 | + self.Release_target_op = ownerOp.par.Targetoperator |
| 31 | + self.Release_version = ownerOp.par.Releaseversion |
| 32 | + self.Reset_color = (0.545, 0.545, 0.545) |
| 33 | + self.Format_ready_save_loc = '{loc}/{name}.tox' |
| 34 | + self.Save_dir = ownerOp.par.Savelocation |
| 35 | + self.Tox_Name = ownerOp.par.Toxname |
| 36 | + self.Destroy_tags = ownerOp.par.Destroytags |
| 37 | + self.Ext_file_tags = ownerOp.par.Externalfiletags |
| 38 | + |
| 39 | + self.saveBuffer = ownerOp.op("base_saveBuffer") |
| 40 | + |
| 41 | + self.GithubLink = "https://github.com/raganmd/touchdesigner-tox-prep-for-release" |
| 42 | + print("PackageTOX Initialized") |
| 43 | + return |
| 44 | + |
| 45 | + def copyToSaveBuffer(self, targetOp): |
| 46 | + quiteCopy = self.saveBuffer.copy(targetOp) |
| 47 | + return quiteCopy |
| 48 | + |
| 49 | + def Package(self): |
| 50 | + msg = "Packing TOX" |
| 51 | + self._log_release_event(msg=msg) |
| 52 | + location = self.Save_tox() |
| 53 | + msg = popupMsg.format(location=location, name=self.Tox_Name) |
| 54 | + |
| 55 | + # if running just a build, force quit |
| 56 | + if self.OwnerOp.par.Quitafterpackaging: |
| 57 | + run(self._force_quit, delayFrames=10) |
| 58 | + |
| 59 | + op('base_popup/container_popup/text_body').text = msg |
| 60 | + op('base_popup/window1').par.winopen.pulse() |
| 61 | + |
| 62 | + def Save_tox(self): |
| 63 | + # format the save location for the tox |
| 64 | + save_loc = self.Format_ready_save_loc.format( |
| 65 | + loc=self.Save_dir, name=self.Tox_Name) |
| 66 | + target_op = self.copyToSaveBuffer(self.Release_target_op.eval()) |
| 67 | + |
| 68 | + # set version |
| 69 | + target_op.par.Version = self.Release_version |
| 70 | + target_op.par.Version.readOnly = True |
| 71 | + |
| 72 | + # clean-up external files - disable loading, remove paths |
| 73 | + ext_file_tags = self.Ext_file_tags.val.split(',') |
| 74 | + ops_to_prep = [each_op for each_op in target_op.findChildren( |
| 75 | + type=DAT) if each_op.par['file'] != ''] |
| 76 | + self.Disable_external_file(ops_to_prep) |
| 77 | + |
| 78 | + # ensure target tox doesn't have a file path |
| 79 | + target_op.par.externaltox = '' |
| 80 | + |
| 81 | + # reset target tox color to be default |
| 82 | + target_op.color = self.Reset_color |
| 83 | + |
| 84 | + # lock the tox icon |
| 85 | + # target_op.op('null_icon').lock = True |
| 86 | + |
| 87 | + # destory ops used for Dev |
| 88 | + destroy_tags = self.Destroy_tags.val.split(',') |
| 89 | + ops_to_destroy = target_op.findChildren(tags=destroy_tags) |
| 90 | + self.Destroy_ops(ops_to_destroy) |
| 91 | + |
| 92 | + # set all custom pars but about page to defaults |
| 93 | + self.SetCustomDefaults(target_op) |
| 94 | + |
| 95 | + # set parent shortcut par to be read only |
| 96 | + target_op.par.parentshortcut.readOnly = True |
| 97 | + |
| 98 | + # set bg top to be read read only |
| 99 | + target_op.par.opviewer.readOnly = True |
| 100 | + |
| 101 | + # hide ops |
| 102 | + self.HideOps(target_op) |
| 103 | + |
| 104 | + # add privacy |
| 105 | + self.AddPrivacy(target_op) |
| 106 | + |
| 107 | + # save TOX in target location |
| 108 | + target_op.save(save_loc) |
| 109 | + |
| 110 | + # destroy the buffer copy |
| 111 | + target_op.destroy() |
| 112 | + |
| 113 | + return save_loc |
| 114 | + |
| 115 | + def HideOps(self, targetOp): |
| 116 | + # hides ops from view |
| 117 | + hideTargets = targetOp.findChildren(tags=['HIDE']) |
| 118 | + if self.OwnerOp.par.Hideops.eval(): |
| 119 | + for eachOp in hideTargets: |
| 120 | + eachOp.expose = False |
| 121 | + msg = f"Hiding op {eachOp.path}" |
| 122 | + self._log_release_event(msg=msg) |
| 123 | + |
| 124 | + pass |
| 125 | + |
| 126 | + def SetCustomDefaults(self, targetOp): |
| 127 | + '''Set Custom Defaults |
| 128 | +
|
| 129 | + Skip all custom pars on about page |
| 130 | + All other custom pars, set to default vals |
| 131 | + ''' |
| 132 | + for eachPar in targetOp.pars(): |
| 133 | + if eachPar.isCustom and eachPar.page != 'About': |
| 134 | + eachPar.val = eachPar.default |
| 135 | + msg = f"Setting default for par {eachPar.name}" |
| 136 | + self._log_release_event(msg=msg) |
| 137 | + else: |
| 138 | + pass |
| 139 | + |
| 140 | + def AddPrivacy(self, targetOp): |
| 141 | + # find all ops tagged for privacy |
| 142 | + privacyOps = targetOp.findChildren(type=COMP, tags=['private']) |
| 143 | + |
| 144 | + for eachPrivateOp in privacyOps: |
| 145 | + print('private Op ', eachPrivateOp) |
| 146 | + |
| 147 | + # if the private flag is on, make all privacy ops private |
| 148 | + if self.OwnerOp.par.Makeprivate.eval(): |
| 149 | + privacyKey = self.OwnerOp.par.Password.eval() |
| 150 | + privacyDev = self.OwnerOp.par.Developer.eval() |
| 151 | + privacyEmail = self.OwnerOp.par.Developeremail.eval() |
| 152 | + eachPrivateOp.addPrivacy(privacyKey, |
| 153 | + developerName=privacyDev, |
| 154 | + developerEmail=privacyEmail) |
| 155 | + else: |
| 156 | + pass |
| 157 | + pass |
| 158 | + |
| 159 | + def Destroy_ops(self, ops_to_destroy): |
| 160 | + |
| 161 | + for each in ops_to_destroy: |
| 162 | + msg = f"destroying op {each.path}" |
| 163 | + self._log_release_event(msg=msg) |
| 164 | + each.destroy() |
| 165 | + |
| 166 | + return ops_to_destroy |
| 167 | + |
| 168 | + def Disable_external_file(self, ops_to_prep): |
| 169 | + |
| 170 | + for each in ops_to_prep: |
| 171 | + try: |
| 172 | + # remove path par for ext |
| 173 | + each.par.file = '' |
| 174 | + # turn off loading on start |
| 175 | + each.par.loadonstart = False |
| 176 | + msg = f"disabling external file for op {each.path}" |
| 177 | + self._log_release_event(msg=msg) |
| 178 | + |
| 179 | + except: |
| 180 | + msg = f'Skipping Op {each.path}' |
| 181 | + self._log_release_event(msg=msg) |
| 182 | + |
| 183 | + return ops_to_prep |
| 184 | + |
| 185 | + def _log_release_event(self, msg: str) -> None: |
| 186 | + automation_msg = { |
| 187 | + "sender": f'TD - {self.Tox_Name.eval()}', "payload": msg} |
| 188 | + print(msg) |
| 189 | + log_file = f"{self.Save_dir.eval()}/log.txt" |
| 190 | + try: |
| 191 | + with open(log_file, "a") as file: |
| 192 | + file.write(f"TD {self.Tox_Name.eval()} | {msg}\n") |
| 193 | + except Exception as e: |
| 194 | + pass |
| 195 | + |
| 196 | + def _force_quit(self): |
| 197 | + msg = f"Quitting TouchDesigner" |
| 198 | + self._log_release_event(msg=msg) |
| 199 | + project.quit(force=True) |
| 200 | + |
| 201 | + def Open_github_link(self): |
| 202 | + webbrowser.open_new_tab(self.GithubLink) |
| 203 | + return self.GithubLink |
0 commit comments