def createGenericJob(node): """ Creates rrJob object and fills in submission information node argument should be of type Hou.Node TODO * for RS: add <customRenVer_redshift> (and one probably for htoa) """ render_node = RenderNode(node) newJob = rrJob() newJob.version = hou.applicationVersionString() newJob.software = "Houdini" newJob.renderer = render_node.render_engine newJob.rendererVersion = render_node.render_engine_version newJob.sceneName = hou.hipFile.path() newJob.sceneOS = getOSString() newJob.seqStart = render_node.parms.start newJob.seqEnd = render_node.parms.end newJob.seqStep = render_node.parms.steps newJob.imageWidth = render_node.parms.res[0] newJob.imageHeight = render_node.parms.res[1] newJob.imageFileName = render_node.parms.output_path newJob.camera = render_node.parms.cam_path newJob.layer = render_node.node_path newJob.channel = render_node.parms.take if render_node.parms.is_stereo: newJob.imageStereoR = "_left" newJob.imageStereoL = "_right" newJob.imageFileName = addStereoTokenToPath(newJob.imageFileName) return newJob
def getRenderEngineVersion(self): """ Returns a string specifying render engine version, or Houdini version if renderer is houdini/mantra TODO * refer user to help via details argument in writeError() """ if self.render_engine.startswith("arnold"): try: import arnold return arnold.AiGetVersionString() except ImportError: msg = 'Failed to import "arnold" python module, htoa is not available.' writeError(msg) raise ImportError(msg) elif self.render_engine.startswith("redshift"): ver = hou.hscript('Redshift_version')[0] if not ver.startswith("Unknown"): return ver else: msg = 'Failed to run "Redshift_version" command, redshift is not available.' writeError(msg) raise ImportError(msg) else: return hou.applicationVersionString()
def run(self): node = hou.selectedNodes()[0] try: PathParm = node.parm("vm_picture").eval() except: PathParm = "" if node.type().name() == "opengl": PathParm = node.parm("picture").eval() PathParm = PathParm.split(".")[0:-2] PathParm.append("$F4.exr") path = "" for i in PathParm: path += i path += "." try: os.mkdir(r"c:\openpic") except: pass f1 = open(r"C:\openpic\mplay.bat", "w") path = r'"C:\Program Files\Side Effects Software\Houdini ' + hou.applicationVersionString( ) + r'\bin\mplay.exe" ' + path[0:-1] + " && exit [/b] [ExitCode]" f1.write("@echo off \n") f1.write(path) f1.close
def kinefxnow(self): var1 = hou.applicationVersionString().split(".") floatvar = float(var1[0] + "." + var1[1]) if floatvar >= 18.5: return True else: return False '''
def getJobConfigParams(self, origin, cData): for i in cData: if i[1] == "programVersion": break else: cData.append([ "information", "programVersion", hou.applicationVersionString() ])
def get_houdini_version(as_string=True): """ Returns version of the executed Houdini :param as_string: bool, Whether to return the stiring version or not :return: variant, int or str """ if as_string: return hou.applicationVersionString() else: return hou.applicationVersion()
def node_OnCreated(node): # Skip asset internal nodes if (node.isInsideLockedHDA() == True): return if enabled(): node_hda_file_path = node.type().definition().libraryFilePath() LYNX_analytics.event_send( hou.applicationPlatformInfo(), hou.applicationName(), hou.applicationVersionString(), hou.licenseCategory().name(), "Plugin", "SideFX/Houdini/otls/" + os.path.split(node_hda_file_path)[-1], str(node.type().name()), 0)
def maybe_show_pvm_warning(self, params): version_major, _, _ = hou.applicationVersionString().split('.') if int(version_major) < 16: # Prior to V 16 Houdini didn't have PySide return True if not 'PREEMPTIBLE' in params['instance_type']: True import pvm_consent_dialog from settings import Settings consent_dialog = pvm_consent_dialog.PvmConsentDialog() return Settings.get().get_pvm_ack() or consent_dialog.prompt()
def maybe_show_pvm_warning(self, params): version_major, _, _ = hou.applicationVersionString().split('.') if int(version_major) < 16: # Prior to V 16 Houdini didn't have PySide return True if not 'PREEMPTIBLE' in params['instance_type']: return True import pvm_consent_dialog from settings import Settings consent_dialog = pvm_consent_dialog.PvmConsentDialog() return Settings.get().get_pvm_ack() or consent_dialog.prompt()
def __init__(self, connected): QtGui.QFrame.__init__(self) self.CLIENT_TYPE = [ HComHoudiniUtils.CLIENT_TYPE.HOUDINI, hou.applicationName() + " " + hou.applicationVersionString() ] self.updateUiThread = HComHoudiniWidgets.UiUpdaterThread() self.updateUiThread.update_ui_signal.connect(self._updateUi) self.updateUiThread.append_message_signal.connect( self._appendMessageToTab) self.updateUiThread.input_data_signal.connect(self._getInputData) self.updateUiThread.data_received_update.connect( self._dataReveivedUpdate) self.updateUiThread.start() self.connected = connected if hou.session.HCOM_TABS != {}: self.USER_TABS = hou.session.HCOM_TABS else: self.USER_TABS = {} if not self.connected: self.hcc = False self.ID = "" else: self.hcc = hou.session.HCOMCLIENT[0] self.ID = hou.session.HCOMCLIENT[1] self.mainLayout = QtGui.QVBoxLayout() self.mainLayout.setSpacing(10) self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) self.centralLayout = QtGui.QVBoxLayout() self.centralWidget = QtGui.QWidget() self.__init_header() self.__init_userList() self.__init_centralWidget() # Set Layout self.splitter.setSizes([100, 500]) self.splitter.setStyleSheet( '''QSplitter::handle:vertical{height: 2px}''') self.mainLayout.addWidget(self.splitter) self.setLayout(self.mainLayout)
def _get_current_package(): """Introspect to find the host software and check that Conductor knows about it. Return a tuple with a useful name like hoodini-16.5.323 and the package object """ hou_version = hou.applicationVersionString() versioned_name = "houdini-%s" % hou_version package = common.get_package_ids().get('houdini').get(hou_version) if not package: raise Exception( """Current houdini version (%s) is not available at Conductor. Please choose one manually.""" % hou_version) return (versioned_name, package)
def updateData(self, packages=None): self.beginResetModel() hversion = Version(hou.applicationVersionString()) items = [] if packages: items = packages else: attempts = 3 while attempts != 0: try: r = requests.get('https://raw.githubusercontent.com/anvdev/' 'Houdini-Package-List/master/data.json') data = json.loads(r.text) if hou.getenv('username') == 'MarkWilson': # Debug only with open(r'C:\Users\MarkWilson\Documents\Houdini-Package-List\data.json') as file: data = json.load(file) break except IOError: attempts -= 1 sleep(0.125) else: data = {} for name, package_data in sorted(data.items(), key=itemgetter(0)): if not package_data.get('visible', True): continue if hversion not in VersionRange.fromPattern(package_data.get('hversion', '*')): continue items.append(WebPackage( name, package_data.get('description'), package_data.get('author'), package_data['source'], package_data['source_type'], package_data.get('hversion'), package_data.get('hlicense'), package_data.get('status'), package_data.get('setup_schema') )) self.__data = tuple(items) self.endResetModel()
def __init__(self, connected): QtGui.QFrame.__init__(self) self.CLIENT_TYPE = [HComHoudiniUtils.CLIENT_TYPE.HOUDINI, hou.applicationName() + " " + hou.applicationVersionString()] self.updateUiThread = HComHoudiniWidgets.UiUpdaterThread() self.updateUiThread.update_ui_signal.connect(self._updateUi) self.updateUiThread.append_message_signal.connect(self._appendMessageToTab) self.updateUiThread.input_data_signal.connect(self._getInputData) self.updateUiThread.data_received_update.connect(self._dataReveivedUpdate) self.updateUiThread.start() self.connected = connected if hou.session.HCOM_TABS != {}: self.USER_TABS = hou.session.HCOM_TABS else: self.USER_TABS = {} if not self.connected: self.hcc = False self.ID = "" else: self.hcc = hou.session.HCOMCLIENT[0] self.ID = hou.session.HCOMCLIENT[1] self.mainLayout = QtGui.QVBoxLayout() self.mainLayout.setSpacing(10) self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) self.centralLayout = QtGui.QVBoxLayout() self.centralWidget = QtGui.QWidget() self.__init_header() self.__init_userList() self.__init_centralWidget() # Set Layout self.splitter.setSizes([100,500]) self.splitter.setStyleSheet('''QSplitter::handle:vertical{height: 2px}''') self.mainLayout.addWidget(self.splitter) self.setLayout(self.mainLayout)
def GetWidgets(self): """ 设置控件 """ #& btn_proj_path加载图标 image = b'<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609656219985" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8030" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css">@font-face { font-weight: 400; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Book-cd7d2bcec649b1243839a15d5eb8f0a3.woff2") format("woff2"); }\n@font-face { font-weight: 500; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Medium-d74eac43c78bd5852478998ce63dceb3.woff2") format("woff2"); }\n@font-face { font-weight: 700; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Bold-83b8ceaf77f49c7cffa44107561909e4.woff2") format("woff2"); }\n@font-face { font-weight: 900; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Black-bf067ecb8aa777ceb6df7d72226febca.woff2") format("woff2"); }\n</style></defs><path d="M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2c-1.5-1.4-3.5-2.2-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256z m635.3 512H159l103.3-256h612.4L771.3 768z" p-id="8031" fill="#dbdbdb"></path></svg>' btn_proj_path_icon = QPixmap() btn_proj_path_icon.loadFromData(image) btn_image = b'<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1610118992486" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2753" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><defs><style type="text/css">@font-face { font-weight: 400; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Book-cd7d2bcec649b1243839a15d5eb8f0a3.woff2") format("woff2"); }@font-face { font-weight: 500; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Medium-d74eac43c78bd5852478998ce63dceb3.woff2") format("woff2"); }@font-face { font-weight: 700; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Bold-83b8ceaf77f49c7cffa44107561909e4.woff2") format("woff2"); }@font-face { font-weight: 900; font-style: normal; font-family: Circular-Loom; src: url("https://cdn.loom.com/assets/fonts/circular/CircularXXWeb-Black-bf067ecb8aa777ceb6df7d72226febca.woff2") format("woff2"); }</style></defs><path d="M832 626.592l-128-128-128 128-256.48-256.448L192 497.632V191.872h640V626.56z m0 205.28H192V588.16l127.52-127.52L576 717.12l128-128 128 128v114.72z m0-704H128v768h768v-768h-64z" fill="#dbdbdb" p-id="2754"></path><path d="M672 319.872c-17.632 0-32 14.368-32 32 0 17.6 14.368 32 32 32 17.632 0 32-14.4 32-32 0-17.632-14.368-32-32-32m0 128c-52.928 0-96-43.072-96-96s43.072-96 96-96 96 43.072 96 96-43.072 96-96 96" fill="#dbdbdb" p-id="2755"></path></svg>' btn_image_icon = QPixmap() btn_image_icon.loadFromData(btn_image) btn_proj_path = self.ui.findChild(QPushButton, "proj_path") self.btn_creatimg = self.ui.findChild(QPushButton, "creatimg") self.cleanimage = self.ui.findChild(QPushButton, "cleanimage") self.folderlist = self.ui.findChild(QComboBox, "path_list") self.path_info = self.ui.findChild(QLabel, "path_info") self.rendererlist = self.ui.findChild(QComboBox, "renderlist") self.hdrilist = self.ui.findChild(QListWidget, "hdrilist") btn_proj_path.setIcon(btn_proj_path_icon) self.btn_creatimg.setIcon(btn_image_icon) self.hdrilist.setViewMode(QListView.IconMode) self.hdrilist.setIconSize(QSize(150, 100)) self.hdrilist.setResizeMode(QListWidget.Adjust) self.path_info.setText(self.hdri) self.hdrilist.customContextMenuRequested[QtCore.QPoint].connect( self.ListWidgetContext) btn_proj_path.clicked.connect(self.set_hdri_folder) if hou.applicationVersionString() > '18.0': self.btn_creatimg.clicked.connect(self.create_image_to_jpg) else: self.btn_creatimg.clicked.connect(self.version) self.cleanimage.clicked.connect(self.delerrorimage) self.folderlist.activated.connect(self.Refresh) self.folderlist.activated.connect(self.CreateInterface) self.renderset() self.Refresh() self.CreateInterface()
def host_info(self): """ :returns: A {"name": application name, "version": application version} dictionary with informations about the application hosting this engine. References: latest: http://www.sidefx.com/docs/houdini/hom/hou/applicationVersion """ host_info = {"name": "houdini", "version": "unknown"} if hasattr(hou, "applicationVersionString"): host_info["version"] = hou.applicationVersionString() else: # Fallback to older way host_info["version"] = ".".join([str(v) for v in hou.applicationVersion()]) if hasattr(hou, "applicationName"): host_info["name"] = hou.applicationName() return host_info
def create(self, com): if com.path[-1] != '/': self.new_item_start.emit(os.path.basename(com.path)) cloud_path = "haoc/nodes/%s" % com.path bucket = SCSBucket(BUCKET_NAME) local_path = "%s/data/%s/nodes/%s" % ( HaocUtils.get_root_path(), HaocUtils.Config.get_ak(), com.path) if cloud_path[-1] == '/': bucket.put(cloud_path, '') else: shall_upload = True if not os.path.exists("%s.nod" % local_path): print "File not found:%s" % local_path return lt = int(os.path.getmtime("%s.nod" % local_path)) if self.from_agenda: try: bucket["%s.nod" % cloud_path] except sinastorage.bucket.KeyNotFound: pass else: info = bucket.info("%s.nod" % cloud_path) ct = int(info.get('metadata').get('lmt')) if lt < ct: shall_upload = False if shall_upload: bucket.putFile("%s.nod" % cloud_path, "%s.nod" % local_path, self.call_back) bucket.update_meta("%s.nod" % cloud_path, {'lmt': str(lt)}) bucket.update_meta("%s.nod" % cloud_path, {'hver': hou.applicationVersionString()}) if os.path.exists("%s.hlp" % local_path): bucket.putFile("%s.hlp" % cloud_path, "%s.hlp" % local_path) else: open("%s.nod" % local_path, 'w').close()
def get_dcc_version(dcc_name): version = '' if dcc_name == 'maya': import maya.cmds version = int(maya.cmds.about(version=True)) elif dcc_name == 'max': from pymxs import runtime max_version = runtime.maxVersion() version = int(2000 + (math.ceil(max_version[0] / 1000.0) - 2)) if max_version[0] < 20000 else int(max_version[7]) elif dcc_name == 'mobu': import pyfbsdk version = int(2000 + math.ceil(pyfbsdk.FBSystem().Version / 1000.0)) elif dcc_name == 'houdini': import hou version = '.'.join(hou.applicationVersionString().split('.')[:-1]) elif dcc_name == 'unreal': import unreal version = '.'.join(unreal.SystemLibrary.get_engine_version().split( '+++')[0].split('-')[0].split('.')[:-1]) return str(version)
def get_basic_meta(self): meta = super(HoudiniObject, self).get_basic_meta() type_name = self.node.type().name() meta.update({ 'application': 'Houdini %s' % hou.applicationVersionString(), 'houdini': { 'path': self.node.path(), 'context': self.node.type().category().name().lower(), 'type': type_name, }, }) if type_name == 'instance': instance = node.parm('instancepath').evalAsString() instance = os.path.abspath(os.path.join(node.path(), instance)) instance = os.path.relpath(instance, os.path.dirname(node.path())) meta['instance'] = instance elif type_name not in ('geo', 'subnet'): raise ValueError('cannot export %r' % type_name) return meta
def get_compare_version(hfs): version_root = os.path.dirname(hfs) versions = os.listdir(version_root) version_num = hou.applicationVersionString() current_version = 'Houdini ' + version_num if current_version in versions: versions.remove(current_version) #UIのオプション用辞書 kwargs = { 'exclusive': True, 'title': 'Select Compare Version', 'column_header': 'Versions' } #Houdini17.0以降はUIのwidth、heightオプションを使う if float('.'.join(version_num.split('.')[:-1])) > 17.0: kwargs['width'] = 240 kwargs['height'] = 240 #バージョンを選択用のリストビューを表示 sel_version = hou.ui.selectFromList(versions, **kwargs) if not sel_version: return version = versions[sel_version[0]] return version
def houVer(): houTypeRaw = hou.applicationName() houApp = hou.isApprentice() houType = "houdiniDefault" addedWarning = 0 if houTypeRaw == "houdini" and houApp == True: houType = "Houdini Apprentice" addedWarning = 1 elif houTypeRaw == "houdini": houType = "Houdini" addedWarning = 0 elif houTypeRaw == "hescape": houType = "Houdini Escape" addedWarning = 1 elif houTypeRaw == "engine": houType = "Houdini Engine" addedWarning = 0 print "Launching " + houType + " Version: " + hou.applicationVersionString( ) if addedWarning == 1: print "Warning: Dynamics not accessible in this version."
def getSoftware(self): '''Determine the software name and version. And define the table where it will be registered. ''' try: import maya.cmds self.params['table'] = 'Maya' return maya.cmds.about(installedVersion=True) except ImportError: pass try: import nuke self.params['table'] = 'Nuke' return nuke.NUKE_VERSION_STRING except ImportError: pass try: import mari.app self.params['table'] = 'Mari' return mari.app.version().string() except ImportError: pass try: import hou self.params['table'] = 'Houdini' return hou.applicationVersionString() except ImportError: pass try: import bpy.app self.params['table'] = 'Blender' return bpy.app.version_string except ImportError: pass self.params['table'] = 'Unknown' return 'Unknown'
#!/usr/bin/env python # -*- coding:UTF-8 -*- # @Time : 2019/6/27 11:55 # @Email : [email protected] # @Name : existsUI.py __author__ = 'miaochenliang' # import--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # import hou import xml from cStringIO import StringIO houdiniVersion = int(hou.applicationVersionString().split('.')[0]) USE_PYQT_MODULE = False if houdiniVersion >= 17: try: from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5 import uic from PyQt5 import sip from PyQt5.QtCore import pyqtSignal USE_PYQT_MODULE = True except ImportError: from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * from PySide2 import shiboken2 as shiboken
def get_version(cls): """e.g. 16.5.323.""" return hou.applicationVersionString()
def renderseq(): """Loop over a string containing frame numbers and render each frame while checking if it exists in the render folder. Various scene relevant pieces of information are stored as file attribute. """ import os.path from os import path import time import os print("\n\n===== renderseq ==================") hou.hipFile.save() # get string of frames to render nums_node = hou.node("/stage/nums") frames = map(int, nums_node.parm("nums").eval().split()) rs_node = hou.selectedNodes()[0] image_path = rs_node.parm("picture").eval() n = rs_node xs = str(rs_node.parmTuple(n.path() + "/resolution")[0].eval()) ys = str(rs_node.parmTuple(n.path() + "/resolution")[0].eval()) imagesize = xs + "_x_" + ys print("frames: ", str(frames)) print("redshift render node (rsNode): ", rs_node) for f in frames: print("\n ... frame ", str(f)) hou.setFrame(f) image_path = rs_node.parm("picture").eval() if path.exists(image_path): print(image_path, " exists") else: print(image_path, " does not exist") rs_node.parm("execute").pressButton() path_is = path.exists(image_path) path_is_not = not path_is while path_is_not: print(" ... waiting for file ... ", image_path) time.sleep(20) path_is = path.exists(image_path) path_is_not = not path_is if path_is: break print("\n out of while for path", image_path) hipfilename = hou.hipFile.path() mycommand = "setfattr -n user.hipfile -v " + hipfilename + " " + image_path mycommand2 = ( "setfattr -n user.frame -v " + str(hou.frame()) + " " + image_path ) mycommand3 = ( "setfattr -n user.houversion -v " + hou.applicationVersionString() + " " + image_path ) mycommand4 = "setfattr -n user.imagesize -v " + imagesize + " " + image_path print("mycommand ", mycommand) print("mycommand2 ", mycommand2) os.system(mycommand) os.system(mycommand2) os.system(mycommand3) os.system(mycommand4) try: # some hard coded paths. Beware. lut = hou.parm("/obj/cam1/RS_campro_lutFile").eval() env = hou.parm("/stage/domelight1/xn__texturefile_0ta").eval() dist = ( hou.node( hou.parm( "/stage/camera1/xn__karmacameralensshadervop_4fbg" ).eval() ) .parm("curv_map") .eval() ) foc = ( hou.node( hou.parm( "/stage/camera1/xn__karmacameralensshadervop_4fbg" ).eval() ) .parm("focus_map") .eval() ) mycommand5 = "setfattr -n user.lut -v " + lut + " " + image_path mycommand6 = "setfattr -n user.env -v " + env + " " + image_path mycommand7 = "setfattr -n user.distort -v " + dist + " " + image_path mycommand8 = "setfattr -n user.focus -v " + foc + " " + image_path os.system(mycommand5) os.system(mycommand6) os.system(mycommand7) os.system(mycommand8) except: pass os.system("shutdown")
def publish_hda(ui, location='job', majorUpdate=True): ''' param: majorUpdate [boolean] - Whether to do a major or minor version up param: location [string] - Where to publish the hda to ('shot', 'job', 'site') return: filepath to hda or false if unsuccessful ''' node = hou.selectedNodes() if not node: print('First select an HDA you want to publish') return node = node[0] # get definition definition = node.type().definition() if not definition: print('Selected node is not an HDA') return # asset name hdaName = node.type().name() hdaPath = definition.libraryFilePath() # //TBA-AD/redirect$/mike.battcock/Documents/houdini16.5\otls\tba_c.hda hdaFileName = os.path.basename(hdaPath) # try and extract version hdaBaseName, hdaVersion = get_hda_version(hdaName) # open file permissions os.chmod(hdaPath, 0o700) # get major, minor and build numbers from houdini version major, minor, build = hou.applicationVersionString().split('.') # published dir if location == 'job': job_data = tbautils.common.parse_job_path(hou.hipFile.path()) if not job_data['job']: hou.ui.displayMessage('You are not currently working in a job.') return config_dir = os.path.join(job_data['job_path'], 'config') # might need to check for this folder first since if we need to recreate it it needs to be a hidden folder publish_dir = os.path.join(config_dir, 'houdini', 'otls') if not os.path.exists(publish_dir): os.makedirs(publish_dir) elif location == 'build/shot': job_data = tbautils.common.parse_job_path(hou.hipFile.path()) if not job_data['entity']: hou.ui.displayMessage('You are not currently working in build or shots.') return # might need to check for this folder first since if we need to recreate it it needs to be a hidden folder publish_dir = os.path.join(job_data['job_path'], 'vfx', job_data['stage'], job_data['entity'], '_published3d', 'otls') if not os.path.exists(publish_dir): os.makedirs(publish_dir) elif location == 'site': publish_dir = os.path.join('S:/3D_globalSettings/houdini/' + major + '.' + minor, 'otls') else: print('Location parameter is invalid. Must be either shot, job or site') return if not os.path.exists(publish_dir): print('Publish directory does not exist: {}'.format(publish_dir)) hou.ui.displayMessage('Publish directory does not exist: {}'.format(publish_dir)) return newHdaPath = os.path.abspath(os.path.join(publish_dir, hdaFileName)).replace('\\','/') if newHdaPath == hdaPath: print('HDA is already published. Checkout first if you want to make changes') hou.ui.displayMessage('HDA is already published. Checkout first if you want to make changes') return # resolve conflicts and get highest version if os.path.exists(newHdaPath): print('Conflicting hda: {}'.format(newHdaPath)) print('Finding conflicting definition version and versioning up based on that') # last one should be latest version (otherwise we could loop through and get their versions) #conflicting_definition = hou.hda.definitionsInFile(newHdaPath)[-1] conflicting_definition = sorted(hou.hda.definitionsInFile(newHdaPath), key=lambda x: float(x.nodeTypeName().split('::')[-1]))[-1] #conflicting_definition = sorted(hou.hda.definitionsInFile(newHdaPath), key=lambda x: x.nodeTypeName())[-1] #print "sorted defs: {}".format(flt_defs[-1]) conflicting_hdaName = conflicting_definition.nodeTypeName() print('Conflicting hdaName: {}'.format(conflicting_hdaName)) # try and extract version hdaBaseName, conflicting_version = get_hda_version(conflicting_hdaName) print('Conflicting version: {}'.format(conflicting_version)) print "hdaVersion".format(hdaVersion) hdaVersion = max(hdaVersion, conflicting_version + 1) # save current node definition.updateFromNode(node) """ if majorUpdate: newVersion = str(math.ceil(hdaVersion+0.01)) else: newVersion = str(hdaVersion + 0.1) """ #newName = hdaName newName = '{}::{}'.format(hdaBaseName, hdaVersion) print "" print('oldHdaPath: {}'.format(hdaPath)) print('oldName: {}'.format(hdaName)) print('newHdaPath: {}'.format(newHdaPath)) print('newName: {}'.format(newName)) # copy the file to the published directory os.chmod(hdaPath, 0o700) definition.copyToHDAFile(str(newHdaPath), str(newName)) # install new hda to houdini session hou.hda.installFile(newHdaPath) # change selection to use new hda node = node.changeNodeType(newName, keep_network_contents=False) # match current definition to lock asset #node.matchCurrentDefinition() # check if filepath clash # set file to read only os.chmod(newHdaPath, 0o444) # Add asset to database db_update_asset(node) # close ui ui.close()
def main(): # Get the path to the curreent scene file hip_file_path = hou.hipFile.path() # Parent dir of folder containing the hip_file_path houdini_dir = os.path.abspath(os.path.join(os.path.dirname(hou.hipFile.path()), os.pardir)) # https://stackoverflow.com/questions/16513573/python-if-var-false if not os.path.exists(houdini_dir+'/flip'): print 'Creating directory: '+houdini_dir+'/flip' os.makedirs(houdini_dir+'/flip') # Put flipbooks in 'flip' folder flipbook_output_path = houdini_dir+'/flip/'+hou.getenv('HIPNAME')+'/$F4.JPEG' #flipbook_output_path = houdini_dir+'/flip/'+hou.getenv('HIPNAME')+'/$N.JPEG' # Save a backup of the scene file to the 'flip' folder as well backup_file_path = saveArchive(houdini_dir+'/flip/'+hou.getenv('HIPNAME')) # Output .mov path mp4_output_path = houdini_dir+'/flip/'+hou.getenv('HIPNAME')+'/'+os.path.splitext(os.path.split(backup_file_path)[1])[0]+'.mp4' # put quotes around this path to handle dir paths with spaces mp4_output_path = '"'+mp4_output_path+'"' if not os.path.exists(os.path.dirname(flipbook_output_path)): print 'Creating directory: '+os.path.dirname(flipbook_output_path) os.makedirs(os.path.dirname(flipbook_output_path)) # Don't modifiy the existing settings (make a copy) flbk_settings = toolutils.sceneViewer().flipbookSettings().stash() # Set the output path print 'Flipbook output path: '+flipbook_output_path flbk_settings.output(flipbook_output_path) flbk_settings.outputToMPlay(0) ''' # This won't preserve aspect ratio !! flbk_settings.useResolution(1) flbk_settings.resolution((512, 128)) ''' start = flbk_settings.frameRange()[0] end = flbk_settings.frameRange()[1] inc = flbk_settings.frameIncrement() ''' hou.getenv('HIPNAME') hou.hipFile.path() hou.getenv('USER') hou.applicationVersionString() hou.applicationPlatformInfo() ''' # Launch the flipbook toolutils.sceneViewer().flipbook(viewport=None, settings=flbk_settings, open_dialog=False) # Make me .JPEGs # We want to use a mild compression, by default Houdini saves them at full/loseless quality so their filesize is huge # Convert the output path to ffmpeg friendly %04d instead of Houdini's $F4 frame syntax flipbook_output_path = string.replace(flipbook_output_path, '$F4', '%04d') flipbook_output_path = '"'+flipbook_output_path +'"' # Make me a .mov # https://stackoverflow.com/questions/14430593/encoding-a-readable-movie-by-quicktime-using-ffmpeg # ffmpeg -i /tmp/%04d.JPEG -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 -vcodec libx264 -pix_fmt yuv420p .v1.mp4 # add -y to the command line after input file path to force overwriting if file exists # See link below for more info about adding metadata with ffmpeg # https://ubuntuforums.org/showthread.php?t=1193808 # You can simply press enter where you want the newline as you are typing your command. # http://ffmpeg.gusari.org/viewtopic.php?f=11&t=3032 # by default ffmpeg outputs 25fps, change this with: -r 24 convert2mov_command = 'ffmpeg' convert2mov_command+=' -i '+string.replace(flipbook_output_path, '$F4', '%04d') convert2mov_command+=' -y' convert2mov_command+=' -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"' convert2mov_command+=' -f mp4 -vcodec libx264 -pix_fmt yuv420p' convert2mov_command+=' -r 24' # Set the Constant Rate Factor of the video # http://slhck.info/video/2017/02/24/crf-guide.html convert2mov_command+=' -crf 22' # Set the bitrate of the vide0 # https://gist.github.com/ksharsha/b06d184391290bc3b87fdadadb73c5bc#file-ffmpeg-compress #convert2mov_command+=' -b:v 750k' convert2mov_command+=' -metadata title="'+hou.getenv('HIPNAME')+'"' # Metadata comment block: convert2mov_command+=' -metadata comment="' convert2mov_command+='SOURCE: '+hou.hipFile.path()+'\n' convert2mov_command+='SNAPSHOT: '+backup_file_path+'\n' convert2mov_command+='USER: '******'USER')+'\n' convert2mov_command+='VERSION: '+hou.applicationVersionString()+'\n' convert2mov_command+='PLATFORM: '+hou.applicationPlatformInfo() convert2mov_command+='"' # Metadata can be viewed using ffprobe, which comes with ffmpeg convert2mov_command+=' '+mp4_output_path print 'running "%s"' % convert2mov_command os.system(convert2mov_command) # Newline #os.system('echo') print 'done' # Taking temporary L on gif feature to finish main part # Make me a .gif #convert2gif_command = '/usr/local/bin/convert -loop 0 ' + src_frames + ' -fuzz 0% -layers Optimize ' + out_gif # Cleanup # Remove the .JPEGs generated by the flipbook # Remove the quotes at the beginning and ending of the path flipbook_output_path = flipbook_output_path.strip('"\'') # Replace the %04d ffmpeg frame indicator with * wildcard flipbook_output_path = flipbook_output_path.translate(None, "'").replace('%04d', '*') # This is done to handle paths that contain spaces, we have to encapsulate the dir path in quotes. # the wildcard part of the command must be outside the quotes, so that's why there's some dirname, basename finessing flipbook_output_path = '"'+os.path.dirname(flipbook_output_path)+'"' + '/' + os.path.basename(flipbook_output_path) command = 'rm -Rf %s' % flipbook_output_path #command+= str(flipbook_output_path) print 'running %s' % command os.system(command)
def node_OnCreated(node): # Skip asset internal nodes if (node.isInsideLockedHDA()==True): return if LYNX_analytics.enabled(lambda : int(1-hou.ui.displayMessage(title="LYNX | Analytics",text="We use Google Analytics to collect data about our tools. \n You can find out more about what data is collected on the LYNX GitHub Page. \n Do you want to enable data collection?", buttons=("Yes", "No")))): node_hda_file_path = node.type().definition().libraryFilePath() LYNX_analytics.event_send(hou.applicationPlatformInfo(),hou.applicationName(),hou.applicationVersionString(),hou.licenseCategory().name(),"Plugin", "SideFX/Houdini/otls/"+os.path.split(node_hda_file_path)[-1], str(node.type().name()), 0)
def process(self, context): import hou houdini_version = hou.applicationVersionString() context.data["houdiniVersion"] = houdini_version.rsplit(".", 1)[0]
def register_plugin(plugin_uid, plugin_info, AssetPushService=None, misc_services={}): # prevent double registration global _http_servers if plugin_uid in _http_servers: raise RuntimeError('add-on already registered') # prepare logger logger = logging.getLogger(plugin_uid) logger.setLevel(logging.INFO) # add console handler if not hasattr(logger, '_has_console_handler'): console_log = logging.StreamHandler() console_log.setLevel(logging.DEBUG) console_log.setFormatter( logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s')) logger.addHandler(console_log) setattr(logger, '_has_console_handler', True) # check if push service is derived properly if AssetPushService is not None: if not issubclass( AssetPushService, assetexchange_shared.server.AssetPushServiceInterface): raise RuntimeError( 'AssetPushService should inherit AssetPushServiceInterface') # setup registry service_registry = {} if AssetPushService is not None: service_registry['assetninja.assetpush#1'] = AssetPushService service_registry.update(misc_services) service_registry = { key: val for key, val in service_registry.items() if val is not None } # setup http protocol handler class HttpServerRequestHandler( assetexchange_shared.server.HttpServerRequestHandler): # copy logger over _logger = logger # override logger getter def get_logger(self): return self._logger # copy service registry over _service_registry = service_registry # override service registry getter def get_service_registry(self): return self._service_registry # start http server using a free port _http_servers[plugin_uid] = ThreadingHTTPServer(('127.0.0.1', 0), HttpServerRequestHandler) thread = threading.Thread(target=_http_servers[plugin_uid].serve_forever) # note: required for houdini exit, otherwhise it will block (even though we have an atexit handler) thread.setDaemon(True) thread.start() # retrieve port (no race condition here, as it is available right after construction) port = _http_servers[plugin_uid].server_address[1] logger.info("port=" + str(port)) # write registration file regfile = assetexchange_shared.server.service_entry_path( 'extension.houdini', plugin_uid) with open(regfile, 'w') as portfile: portfile.write( json.dumps( { 'category': 'extension.houdini', 'type': plugin_uid, 'pid': os.getpid(), 'port': port, 'protocols': ['basic'], 'info': { 'extension.uid': plugin_uid, 'extension.name': plugin_info['name'], 'extension.description': plugin_info['description'], 'extension.author': plugin_info['author'], 'extension.version': plugin_info['version'], 'houdini.executable': sys.executable, 'houdini.version': hou.applicationVersionString(), }, 'services': list(service_registry.keys()), }, indent=2))
def publish_hda(majorUpdate=True, location='job', filepath=None): ''' param: majorUpdate [boolean] - Whether to do a major or minor version up param: location [string] - Where to publish the hda to ('shot', 'job', 'site') return: filepath to hda or false if unsuccessful ''' node = hou.selectedNodes() if not node: print('First select an HDA you want to publish') return node = node[0] # get definition definition = node.type().definition() if not definition: print('Selected node is not an HDA') return # asset name hdaLabel = node.type().name() hdaPath = definition.libraryFilePath( ) # //TBA-AD/redirect$/mike.battcock/Documents/houdini16.5\otls\tba_c_1.0.hda hdaVersion = definition.version() # get major, minor and build numbers from houdini version major, minor, build = hou.applicationVersionString().split('.') # published dir if location == 'job': job_data = common.parse_job_path(hou.hipFile.path()) config_dir = os.path.join(job_data['job_path'], 'config') # might need to check for this folder first since if we need to recreate it it needs to be a hidden folder publish_dir = os.path.join(config_dir, 'houdini', 'otls') if not os.path.exists(publish_dir): os.makedirs(config_dir) elif location == 'shot': job_data = common.parse_job_path(hou.hipFile.path()) # might need to check for this folder first since if we need to recreate it it needs to be a hidden folder publish_dir = os.path.join(job_data['job_path'], 'vfx', job_data['stage'], job_data['entity'], '_published3d', 'otls') if not os.path.exists(publish_dir): os.makedirs(config_dir) elif location == 'site': publish_dir = os.path.join( 'S:/3D_globalSettings/houdini/' + major + '.' + minor, 'otls') else: print( 'Location parameter is invalid. Must be either shot, job or site') return print('Publish directory: {0}'.format(publish_dir)) # check if selected asset is already published if publish_dir in hdaPath: print( 'Asset is already in the published directory. Check it out if you want to make changes' ) return basename = os.path.splitext(os.path.basename(hdaPath))[0] try: parts = basename.split('_') curVersion = float(parts[-1]) except: print('Could not extract the version number from the hda....: {0}'. format(str(curVersion))) return if majorUpdate: newVersion = curVersion + 1.0 else: newVersion = curVersion + 0.1 # rebuild filename filename = '_'.join(parts[:-1]) + '_' + str(newVersion) + '.hda' # Choose whether to use default generated filepath, or the path # passed as an argument to this function if filepath is None: newHdaPath = os.path.join(publish_dir, filename) else: newHdaPath = filepath #if os.path.exists(newHdaPath): # print('Filepath already exists.. {0}'.format(newHdaPath)) # copy the file to the published directory try: definition.copyToHDAFile(newHdaPath) except hou.OperationFailed: print "Could not write HDA to {}. Check permissions or if it already exists.".format( newHdaPath) # install new hda to houdini session try: hou.hda.installFile(newHdaPath) except hou.OperationFailed: print "Could not install HDA. Is it already in the scene?" # change selection to use new hda #node.changeNodeType(newVersion, keep_network_contents=False) # match current definition to lock asset node.matchCurrentDefinition() # set file to read only try: os.chmod(newHdaPath, S_IREAD | S_IRGRP | S_IROTH) except: print "Could not set permissions on {}".format(newHdaPath) return newHdaPath
def submitJob(self, outputName): osFolder = None lmode = self.core.getConfig("globals", "localmode") if lmode == "True": localmode = True else: localmode = False if localmode: rootPath = self.core.getConfig("globals", "rootpath") if rootPath is not None: osFolder = os.path.join(rootPath, "PandoraFarm", "Workstations", "WS_" + socket.gethostname()) else: osFolder = self.core.getConfig('submissions', "submissionpath") if osFolder is None: return "Submission canceled: No Pandora submission folder is configured." if osFolder == "": return "Submission canceled: No Pandora submission folder is configured." if not os.path.exists(osFolder): try: os.makedirs(osFolder) except: return "Submission canceled: Pandora submission folder could not be created." fileName = self.core.getCurrentFileName() if not os.path.exists(fileName): return "Submission canceled: Please save the scene first." if self.core.app == 2: renderNode = None try: if self.node.type().name() in ["ifd", "Redshift_ROP"]: renderNode = self.node.path() except: pass if renderNode is None: return "Submission canceled: Node is invalid." projectName = self.e_projectName.text() jobName = self.e_jobName.text() startFrame = self.sp_rangeStart.value() endFrame = self.sp_rangeEnd.value() renderCam = self.cb_cam.currentText() overrideResolution = self.chb_resOverride.isChecked() resolutionWidth = self.sp_resWidth.value() resolutionHeight = self.sp_resHeight.value() priority = self.sp_priority.value() framesPerTask = self.sp_framesPerTask.value() subspended = self.chb_suspended.isChecked() submitDependendFiles = self.chb_dependencies.isChecked() uploadOutput = self.chb_uploadOutput.isChecked() useProjectAssets = True listSlaves = "All" userName = self.core.getConfig("submissions", "username") if userName is None: userName = "" programName = self.core.programName # if self.gb_outputpath.isChecked(): outputbase = self.e_outputpath.text() if os.path.splitext(outputbase)[1] != "": outputbase = os.path.dirname(outputbase) # else: # outputpath = "default" self.saveSettings() if projectName == "": return "Submission canceled: Projectname is invalid." if jobName == "": return "Submission canceled: Jobname is invalid." if renderCam == "": return "Submission canceled: Camera is invalid." assignPath = os.path.join(osFolder, "JobSubmissions") jobCode = ''.join(random.choice(string.ascii_lowercase) for x in range(10)) jobPath = os.path.join(assignPath, jobCode , "JobFiles") while os.path.exists(jobPath): jobCode = ''.join(random.choice(string.ascii_lowercase) for x in range(10)) jobPath = os.path.join(assignPath, jobCode , "JobFiles") jobIni = os.path.join(os.path.dirname(jobPath), "PandoraJob.ini") if os.path.exists(jobPath): return "Submission canceled: Job already exists" self.core.saveScene() os.makedirs(jobPath) if useProjectAssets: assetPath = os.path.join(assignPath, "ProjectAssets", projectName) if not os.path.exists(assetPath): os.makedirs(assetPath) else: assetPath = jobPath jobFiles = [[os.path.basename(fileName), os.path.getmtime(fileName)]] if submitDependendFiles: if self.core.app == 6: bpy.ops.file.pack_all() else: extFiles = self.getExternalFiles() tFilesState = "None" while True: erFiles = [] while True: tFiles = [] for i in extFiles: if not os.path.exists(i): continue tPath = os.path.join(assetPath, os.path.basename(i)) if os.path.exists(tPath): if tFilesState != "Overwrite": if tFilesState == "Skip": continue if tFilesState == "Keep newest": if int(os.path.getmtime(i)) <= int(os.path.getmtime(tPath)): continue else: if int(os.path.getmtime(i)) != int(os.path.getmtime(tPath)): tFiles.append(i) if os.path.basename(i) not in jobFiles: jobFiles.append([os.path.basename(i), os.path.getmtime(i)]) continue try: shutil.copy2(i, assetPath) if os.path.basename(i) not in jobFiles: jobFiles.append([os.path.basename(i), os.path.getmtime(i)]) except: erFiles.append(i) if len(tFiles) > 0: fString = "Some assets already exist in the ProjectAsset folder and have a different modification date:\n\n" for i in tFiles: fString += "%s\n" % i msg = QMessageBox(QMessageBox.Warning, "Pandora job submission", fString, QMessageBox.Cancel) msg.addButton("Keep newest", QMessageBox.YesRole) msg.addButton("Overwrite", QMessageBox.YesRole) msg.addButton("Skip", QMessageBox.YesRole) self.core.parentWindow(msg) action = msg.exec_() if action == 1: extFiles = tFiles tFilesState = "Overwrite" elif action == 2: tFilesState = "Skip" break elif action != 0: if os.path.exists(jobPath): try: os.remove(jobPath) except: pass return "Submission canceled: Canceled by user" else: extFiles = tFiles tFilesState = "Keep newest" else: tFilesState = "Skip" break if len(erFiles) > 0: fString = "An error occurred while copying external files:\n\n" for i in erFiles: fString += "%s\n" % i msg = QMessageBox(QMessageBox.Warning, "Pandora job submission", fString, QMessageBox.Cancel) msg.addButton("Retry", QMessageBox.YesRole) msg.addButton("Continue", QMessageBox.YesRole) self.core.parentWindow(msg) action = msg.exec_() if action == 1: break elif action != 0: if os.path.exists(jobPath): try: os.remove(jobPath) except: pass return "Submission canceled: Canceled by user" else: extFiles = erFiles else: break while True: try: if self.core.app == 6 and submitDependendFiles: jobFilePath = os.path.join(jobPath, self.core.getCurrentFileName(path=False)) bpy.ops.wm.save_as_mainfile(filepath=jobFilePath, copy=True) bpy.ops.wm.revert_mainfile() else: shutil.copy2(fileName, jobPath) break except Exception as e: msg = QMessageBox(QMessageBox.Warning, "Pandora job submission", "An error occurred while copying the scenefile.\n\n%s" % e, QMessageBox.Cancel) msg.addButton("Retry", QMessageBox.YesRole) msg.addButton("Skip", QMessageBox.YesRole) self.core.parentWindow(msg) action = msg.exec_() if action == 1: break elif action != 0: return "Submission canceled: Could not copy the scenefile" if not useProjectAssets and len(jobFiles) != len(os.listdir(jobPath)): return "Submission canceled: The filecount in the jobsubmission folder is not correct. %s of %s" % (len(os.listdir(jobPath)), len(jobFiles)) if not os.path.exists(jobIni): open(jobIni, 'a').close() config = ConfigParser() config.read(jobIni) config.add_section('jobglobals') config.set('jobglobals', 'priority', str(priority)) config.set('jobglobals', 'uploadOutput', str(uploadOutput)) config.set('jobglobals', 'listslaves', listSlaves) config.set('jobglobals', 'taskTimeout', str(self.sp_rjTimeout.value())) config.add_section('information') config.set('information', 'jobname', jobName) config.set('information', 'scenename', os.path.basename(fileName)) config.set('information', 'projectname', projectName) config.set('information', 'username', userName) config.set('information', 'submitdate', time.strftime("%d.%m.%y, %X", time.localtime())) config.set('information', 'framerange', "%s-%s" % (startFrame, endFrame)) config.set('information', 'outputpath', outputName) config.set('information', 'filecount', str(len(jobFiles))) config.set('information', 'savedbasepath', outputbase) config.set('information', 'outputbase', outputbase) config.set('information', 'program', programName) if self.core.app == 2: config.set('information', 'programversion', hou.applicationVersionString()) config.set('jobglobals', 'rendernode ', renderNode) elif self.core.app == 3: config.set('information', 'programversion', cmds.about(version=True)) if renderCam not in ["", "Current View"]: config.set('information', 'camera', renderCam) if useProjectAssets: config.set('information', 'projectassets', str(jobFiles)) if overrideResolution: config.set('jobglobals', "width", str(resolutionWidth)) config.set('jobglobals', "height", str(resolutionHeight)) config.add_section('jobtasks') curFrame = startFrame tasksNum = 0 if subspended: initState = "disabled" else: initState = "ready" while curFrame <= endFrame: taskStart = curFrame taskEnd = curFrame + framesPerTask - 1 if taskEnd > endFrame: taskEnd = endFrame config.set('jobtasks', 'task'+ str(tasksNum), str([taskStart, taskEnd, initState, "unassigned", "", "", ""])) curFrame += framesPerTask tasksNum += 1 with open(jobIni, 'w') as inifile: config.write(inifile) msg = QMessageBox(QMessageBox.Information, "Submit Pandora renderjob", "Successfully submited job \"%s\"" % jobName, QMessageBox.Ok) msg.addButton("Open in explorer", QMessageBox.YesRole) self.core.parentWindow(msg) action = msg.exec_() if action == 0: self.core.openFolder(os.path.dirname(jobPath)) return "Success"
'beauty', resolution=resolution, crop_window=(0, 0, resolution[0], resolution[1]), camera=camera.get('name')) parm = {'diskfile': SohoParm('soho_diskfile', 'string', ['*'], False)} parmlist = soho.evaluate(parm) filename = parmlist['soho_diskfile'].Value[0] # We can't emit file to stdout, because appleseed.cli currently doesn't accept stdit # with open(filename, 'w') as file: # technically preambule is not part of project object: date = datetime.strftime(datetime.today(), "%b %d, %Y at %H:%M:%S") stat = '<!-- Generation time: %g seconds -->' % (time.time() - clockstart) preambule = APSsettings.PREAMBULE.format( houdini_version=hou.applicationVersionString(), aps_version=APSsettings.__version__, date=date, renderer_version=APSsettings.__appleseed_version__, driver=soho.getOutputDriver().getName(), hipfile=hou.hipFile.name(), TIME=now, FPS=FPS, ) if mode == "update": xform = [] cam.evalFloat("space:world", now, xform) xform = hou.Matrix4(xform).transposed().asTuple() xform = " ".join(map(str, xform)) print xform