def execute(self, operation, file_path, **kwargs): """ Main hook entry point :operation: String Scene operation to perform :file_path: String File path to use if the operation requires it (e.g. open) :returns: Depends on operation: 'current_path' - Return the current scene file path as a String all others - None """ if operation == "current_path": project = fx.activeProject() return project.path elif operation == "open": fx.loadProject(file_path) elif operation == "save": project = fx.activeProject() project.save()
def execute(self): beginUndo("KMFX Clone Reset transforms and frame") node = activeNode() if node.type == "PaintNode": fx.activeProject().save() # small hack to force state to update clonelist = ["0", "1"] # both clone presets for n in clonelist: if fx.prefs["KMFX.Reset Clone also resets opacity"] is True: fx.paint.setState('opacity', 100) if node.state['paint']['Clone.frameRelative:' + n] is True: fx.paint.setState('Clone.frame:' + n, 0) else: fx.paint.setState('Clone.frame:' + n, player.frame) fx.paint.setState('Clone.position:' + n, Point3D(0, 0)) fx.paint.setState('Clone.rotate:' + n, 0) fx.paint.setState('Clone.scale:' + n, Point3D(1, 1)) fx.activeProject().save() endUndo()
def scan_scene(self): """ The scan scene method is executed once at startup and its purpose is to analyze the current scene and return a list of references that are to be potentially operated on. The return data structure is a list of dictionaries. Each scene reference that is returned should be represented by a dictionary with three keys: - "node": The name of the 'node' that is to be operated on. Most DCCs have a concept of a node, path or some other way to address a particular object in the scene. - "type": The object type that this is. This is later passed to the update method so that it knows how to handle the object. - "path": Path on disk to the referenced object. Toolkit will scan the list of items, see if any of the objects matches any templates and try to determine if there is a more recent version available. Any such versions are then displayed in the UI as out of date. """ reads = [] project = fx.activeProject() # find source items in project for item in project.items: if isinstance(item, fx.SourceItem): source = item.source # label is human readable, but only id is unique reads.append({"node": (source.label, item.id), "type": "source", "path": source.property("path").value}) return reads
def run(): import fx from tools.sequenceBuilder import SequenceBuilder # Grab the current project project = fx.activeProject() # Check the current selection node = fx.activeNode() print('[Output To Source]') print('\t[Node Name] ' + node.label) # Process a source node if node.type == 'OutputNode': # Find the active OutputNode path path = GetOutput(node) print('\t[Image] ' + path) # Create an image sequence from the url builder = SequenceBuilder(path) # Load the media into the project src = fx.Source(builder.path) # Add the other image types project.addItem(src) else: print('\t[Error] Select a Source or Output Node.')
def collect_current_silhouette_session(self, settings, parent_item): """ Analyzes the current session open in Maya and parents a subtree of items under the parent_item passed in. :param dict settings: Configured settings for this collector :param parent_item: Root item instance """ # get the current path active_project = fx.activeProject() if not active_project or not active_project.path: # the session has not been saved before (no path determined). # provide a save button. the session will need to be saved before # validation will succeed. self.logger.warning("The silhouette project has not been saved.", extra=self._get_save_as_action()) file_path = active_project.path # Define the item's properties properties = {} session_item = self._add_file_item(settings, parent_item, file_path, False, None, "Current Silhouette Session", "silhouette.session", parent_item.context, properties) self.logger.info("Collected item: %s" % session_item.name) return session_item
def run(): import fx print( '\n\n---------------------------------------------------------------------------------' ) print('Project Explorer') print( '---------------------------------------------------------------------------------\n' ) # Get the Project project = fx.activeProject() print('[Project]') print('\n\t[Project Path] ' + str(project.label)) print('\n\t[Project Version] ' + str(project.version)) print('\n\t[Project Sources]') for s in project.sources: print('\t\t[Sources] ' + str(s.label)) print('\n\t[Project Sessions]') for s in project.sessions: print('\t\t[Sesssion] ' + str(s.label)) # List all properties print('\n\t[Project Properties]') for i in range(len(project.properties.keys())): key = project.properties.keys()[i] print('\t\t[Property] ' + str(key) + ' [Value] "' + str(project.properties[key].value) + '"')
def SaveProject(): import fx # Get the Project project = fx.activeProject() # Save the project project.save()
def StackVertical(): import fx tool = 'Stack Vertical' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = len(str(len(sel))) # Spacing distance between stacked nodes nodeSpacing = 100 # Use the first selected object as a reference for the Node Y position referenceY = 0 if len(sel) > 0: referenceY = sel[0].state.items()[1][1].y print('[Reference Y] ' + str(referenceY)) # Scan all of the selected nodes i = 0 for node in sel: # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if (node.state is not None) and (node != sel[0]): i = i + 1 # The Point3D(0,0) datatype has .x and .y attributes pos = node.state.items()[1][1] if pos is not None: # Stack the nodes side by side node.setState( 'graph.pos', fx.Point3D(pos.x, (referenceY + (i * nodeSpacing)))) # Read back the results posUpdate = node.state.items()[1][1] if posUpdate is not None: print('[' + str(i).zfill(padding) + '] ' + str(node.label) + ' [Original] [X]' + str(pos.x) + ' [Y] ' + str(pos.y) + ' [Updated] [X]' + str(posUpdate.x) + ' [Y] ' + str(posUpdate.y)) else: print('[Error] Please select 2 or more nodes.') fx.endUndo() # SaveProject() # hide the window snapWindow.hide()
def SnapToGrid(): import fx tool = 'Snap to Grid' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = len(str(len(sel))) i = 0 for node in sel: i = i + 1 # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if node.state is not None: # The Point3D(0,0) datatype has .x and .y attributes pos = node.state.items()[1][1] if pos is not None: # Snap the nodes to a 10 unit grid #snapX = round(pos.x, -1) #snapY = round(pos.y, -1) # Snap to 50 grid units snapX = round(float(pos.x) * 2, -2) * 0.5 snapY = round(float(pos.y) * 2, -2) * 0.5 # Snap to 100 grid units on X and 50 gird units on Y snapX = round(pos.x, -2) snapY = round(float(pos.y) * 2, -2) * 0.5 # Snap the nodes to a 100 unit grid #snapX = round(pos.x, -2) #snapY = round(pos.y, -2) # Update the grid snapped node position node.setState('graph.pos', fx.Point3D(snapX, snapY)) posUpdate = node.state.items()[1][1] if posUpdate is not None: print('[' + str(i).zfill(padding) + '] ' + str(node.label) + ' [Original] [X]' + str(pos.x) + ' [Y] ' + str(pos.y) + ' [Updated] [X]' + str(posUpdate.x) + ' [Y] ' + str(posUpdate.y)) fx.endUndo() # SaveProject() # hide the window snapWindow.hide()
def AlignHorizontal(): import fx tool = 'Align Horizontal' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = len(str(len(sel))) # Use the first selected object as a reference for the Node X position referenceX = 0 if len(sel) > 0: referenceX = sel[0].state.items()[1][1].x print('[Reference X] ' + str(referenceX)) # Scan all of the selected nodes i = 0 for node in sel: i = i + 1 # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if node.state is not None: # The Point3D(0,0) datatype has .x and .y attributes pos = node.state.items()[1][1] if pos is not None: # Snap all the nodes to the same X height node.setState('graph.pos', fx.Point3D(referenceX, pos.y)) # Read back the results posUpdate = node.state.items()[1][1] if posUpdate is not None: print('[' + str(i).zfill(padding) + '] ' + str(node.label) + ' [Original] [X]' + str(pos.x) + ' [Y] ' + str(pos.y) + ' [Updated] [X]' + str(posUpdate.x) + ' [Y] ' + str(posUpdate.y)) else: print('[Error] Please select 2 or more nodes.') fx.endUndo() # SaveProject() # hide the window snapWindow.hide()
def _create_source(self, path, sg_publish_data): engine = self.parent.engine # silhouette requires format /path/to/file.[start-end].ext formatted_path, errors = engine.utils.seq_path_to_silhouette_format( self.sgtk, path) if errors: engine.utils.warn_with_pop_up(self.parent.logger, "Image path used as is", errors) source = fx.Source(formatted_path) # add source to project project = fx.activeProject() project.addItem(source)
def AlignByCSV(): import fx import csv path = '/Applications/SilhouetteFX/Silhouette v7.5/Silhouette.app/Contents/Resources/scripts/node_shape.csv' tool = 'Align By CSV' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = 4 # How many nodes are selected count = len(sel) # Prepare CSV reading with open(path, 'rb') as fp: reader = csv.reader(fp, delimiter=',') i = 0 # Scan all of the selected nodes for row in reader: # Move onto the next node # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if i < count: node = sel[i] if (node.state is not None): # The Point3D(0,0) datatype has .x and .y attributes node.setState('graph.pos', fx.Point3D(float(row[0]), float(row[1]))) #posUpdate = node.state.items()[1][1] #if posUpdate is not None: # Read back the results # print('{0},{1:.03f},{2:.03f}'.format(str(i).zfill(padding), posUpdate.x, posUpdate.y)) i = i + 1 fx.endUndo() # SaveProject() # hide the window snapWindow.hide()
def _save_session(self, path, version, item): """ Save the current session to the supplied path. """ ensure_folder_exists(os.path.dirname(path)) active_project = fx.activeProject() if path != active_project.path: save_path = self.parent.engine.utils.get_stripped_project_path( path) active_project.save(save_path) else: active_project.save() # Save the updated property item.properties.path = path
def update(self, items): """ Perform replacements given a number of scene items passed from the app. Once a selection has been performed in the main UI and the user clicks the update button, this method is called. The items parameter is a list of dictionaries on the same form as was generated by the scan_scene hook above. The path key now holds the that each node should be updated *to* rather than the current path. """ engine = self.parent.engine # TODO: is there a way to find input nodes from source/source_item? project = fx.activeProject() source_node_mapping = {} for session in project.sessions: for node in session.inputs: source_id = node.property("stream.primary").value.id node_list = source_node_mapping.setdefault(source_id, []) node_list.append(node) for i in items: node_id = i["node"] node_type = i["type"] new_path = i["path"] if node_type == "source": source_label, source_item_id = node_id engine.log_debug("Path for source %s: Updating to version %s" % (source_label, new_path)) # update source.property("path") source_item = fx.findObject(source_item_id) path_property = source_item.source.property("path") formatted_path, errors = engine.utils.seq_path_to_silhouette_format(self.sgtk, new_path) if errors: engine.utils.warn_with_pop_up(self.parent.logger, "Image path used as is", errors) path_property.setValue(formatted_path) # update source and node labels. # TODO: does silhouette ever have anything in the trailing brackets? label = os.path.basename(formatted_path) + " []" # TODO: the display doesn't auto-refresh source_item.label = label for node in source_node_mapping[source_item.source.id]: node.label = label
def SourcesImport(url): import os import fx from tools.sequenceBuilder import SequenceBuilder # Grab the current project project = fx.activeProject() # Catch an error when loading media try: # Create an image sequence from the url builder = SequenceBuilder(url) # Load the media into the project src = fx.Source(builder.path) # Display the results statusMsg = '[Import Sources] "' + str(builder.path) + '"' fx.status(statusMsg) print(statusMsg) # Layer Names print('\t[Layers] ' + str(src.layers)) # Add the images if src.layers is not None: # Split the EXR multi-part and multi-channel layers apart for layer in src.layers: print '\t\t[Creating Layer] ' + layer s = fx.Source(builder.path) project.addItem(s) # Rename the split EXR layer # s.label = str(layer) s.label = str(s.label) + '_' + str(layer) # Change the source layer if layer != 'default': s.property('layer').value = layer else: # Add the other image types project.addItem(src) except Exception as e: print e
def SaveByCSV(): import fx import csv path = '/Applications/SilhouetteFX/Silhouette v7.5/Silhouette.app/Contents/Resources/scripts/node_shape.csv' # Prepare CSV writing with open(path, 'wb') as fp: writer = csv.writer(fp, delimiter=',') # writer.writerow(['X', 'Y']) tool = 'Save CSV' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = len(str(len(sel))) if len(sel) > 1: # Scan all of the selected nodes for node in sel: # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if node.state is not None: # The Point3D(0,0) datatype has .x and .y attributes pos = node.state.items()[1][1] if pos is not None: # Read back the results # print('{0:.03f},{1:.03f}'.format(pos.x, pos.y)) writer.writerow([pos.x, pos.y]) else: print('[Error] Please select 2 or more nodes.') fx.endUndo()
def _get_dependency_paths(self, node=None): """ Find all dependency paths for the current node. If no node specified, will return all dependency paths for the session. :param node: Optional node to process :return: List of upstream dependency paths """ engine = self.parent.engine dependency_paths = set() # let's look at sources in the project active_project = fx.activeProject() for source in active_project.sources: source_path = source.property("path").value formatted_path, errors = engine.utils.seq_path_from_silhouette_format( self.sgtk, source_path) if errors: self.parent.log_error(errors) dependency_paths.add(formatted_path) return list(dependency_paths)
def execute(self, operation, file_path, context, parent_action, file_version, read_only, **kwargs): """ Main hook entry point :param operation: String Scene operation to perform :param file_path: String File path to use if the operation requires it (e.g. open) :param context: Context The context the file operation is being performed in. :param parent_action: This is the action that this scene operation is being executed for. This can be one of: - open_file - new_file - save_file_as - version_up :param file_version: The version/revision of the file to be opened. If this is 'None' then the latest version should be opened. :param read_only: Specifies if the file should be opened read-only or not :returns: Depends on operation: 'current_path' - Return the current scene file path as a String 'reset' - True if scene was reset to an empty state, otherwise False all others - None """ active_project = fx.activeProject() temp_dir = os.path.realpath(tempfile.gettempdir()) if operation == "current_path": # return the current project path if active_project: return active_project.path else: return None elif operation == "open": fx.loadProject(file_path) elif operation == "save": active_project.save() elif operation == "save_as": initial_project_path = os.path.realpath(active_project.path) save_path = self.parent.engine.utils.get_stripped_project_path( file_path) active_project.save(save_path) # delete earlier project directory if it was a temporary path if initial_project_path.startswith(temp_dir): shutil.rmtree( os.path.dirname(os.path.dirname(initial_project_path))) elif operation == "reset": """ Reset the scene to an empty state """ # save activeProject exit_and_call_save = False if active_project: if active_project.path: project_dir = os.path.realpath( os.path.dirname(active_project.path)) if not project_dir.startswith(temp_dir): active_project.save() else: exit_and_call_save = True else: exit_and_call_save = True if exit_and_call_save: res = QtGui.QMessageBox.question( QtGui.QApplication.activeWindow(), "Save your scene?", "Your scene has unsaved changes. Save before proceeding?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel) if res == QtGui.QMessageBox.Cancel: return False elif res == QtGui.QMessageBox.No: pass else: # there is an active project with no path or temp path - save it # there is no silhouette python API to open a save file GUI, so just use sgtk self.parent.engine.commands["File Save..."]["callback"]() return False if parent_action == "new_file": # do new file. silhouette doesn't do unnamed projects well new_project_name = os.path.join(tempfile.mkdtemp(), "tk_silhouette_project") new_project = fx.Project(new_project_name) # silhouette 7 has method setActiveProject(), which can take None fx.activate(new_project) new_project.save() # make the user save the file immediately, # so that we can avoid using the temp location self.parent.engine.commands["File Save..."]["callback"]() return True
def DistributeSpacesHorizontal(): import fx tool = 'Distribute Spaces Horizontal' PrintStatus(tool) fx.beginUndo(tool) # Get the Project project = fx.activeProject() # Get the node selection sel = fx.selection() # Padding width for node count padding = len(str(len(sel))) # How many does were selected nodeCount = len(sel) # Use the first selected object as a reference for the Node position referenceStartX = 0 referenceEndX = 0 if nodeCount > 0: print('[Selected Nodes] ' + str(nodeCount)) referenceStartX = sel[0].state.items()[1][1].x print('[Reference Start X] ' + str(referenceStartX)) referenceEndX = sel[nodeCount - 1].state.items()[1][1].x print('[Reference End X] ' + str(referenceEndX)) # Start/End Node Distance nodeDistance = abs(referenceStartX - referenceEndX) print('[Node Distance X] ' + str(nodeDistance)) # Spacing distance between stacked nodes nodeSpacing = (nodeDistance) / (nodeCount - 1) print('[Node Spacing X] ' + str(nodeSpacing)) # Scan all of the selected nodes i = 0 for node in sel: # The node.state dict holds {'viewMode': 0, 'graph.pos': Point3D(394.641,22.4925)} if (node.state is not None) and (node != sel[0]) and ( node != sel[nodeCount - 1]): # if (node.state is not None): i = i + 1 # The Point3D(0,0) datatype has .x and .y attributes pos = node.state.items()[1][1] if pos is not None: # Stack the nodes side by side node.setState( 'graph.pos', fx.Point3D((referenceStartX + (i * nodeSpacing)), pos.y)) # Read back the results posUpdate = node.state.items()[1][1] if posUpdate is not None: print('[' + str(i).zfill(padding) + '] ' + str(node.label) + ' [Original] [X]' + str(pos.x) + ' [Y] ' + str(pos.y) + ' [Updated] [X]' + str(posUpdate.x) + ' [Y] ' + str(posUpdate.y)) else: print('[Error] Please select 2 or more nodes.') fx.endUndo() # SaveProject() # hide the window snapWindow.hide()
def execute(self, **kwargs): # fx.beginUndo("KMFX Paint Presets") # undo is not working on this paint_presets_path = fx.prefs["KMFX.Paint Presets Path"] if fx.prefs[ "KMFX.Paint Presets Path"] != "" else os.environ[ "SFX_SCRIPT_PATH"] + "/KMscripts/paint_presets/" #### check if the custom pref path exists and warn user if its wrong if paint_presets_path == fx.prefs["KMFX.Paint Presets Path"]: if not os.path.exists(paint_presets_path): displayError( "The custom path '%s' could be wrong or\nwas not found or can't be read,\nplease check your KMFX preferences!\nFalling back to default path\n %s " % (paint_presets_path, os.environ["SFX_SCRIPT_PATH"] + "/KMscripts/paint_presets/")) # print("The custom path '%s' could be wrong / was not found / can't be read, please check your preferences\n falling back to default path\n %s " % (paint_presets_path,os.environ["SFX_SCRIPT_PATH"] + "/KMscripts/paint_presets/")) paint_presets_path = os.environ[ "SFX_SCRIPT_PATH"] + "/KMscripts/paint_presets/" mode = kwargs["mode"] if "mode" in kwargs.keys() else "save" node = activeNode() if node.type == "PaintNode": ''' the actual brush used it saved on the <item type="string" id="brush"> on the preset. looks like the settings for the rest of the preset are not necessary ''' fx.activeProject().save( ) ##small hack to force the state to update if mode == "save": fname = { "id": "fname", "label": "Filename", "value": "Default" } result = getInput(fields=[fname]) current = fx.paint.preset override = False if result != None: dpath = paint_presets_path + "/" + result["fname"] + "/" directory = os.path.dirname(dpath) if os.path.exists(directory): ov = askQuestion("Preset already exists, override?") if ov == False: return # do not use this with UNDO try: if not os.path.exists(directory): os.makedirs(directory) except: print( "Error creating preset directory, check folder write permissions?\n %s" % directory) for i in range(0, 10): fpath = paint_presets_path + "/" + result[ "fname"] + "/" + result["fname"] + "_" + str( i) + '.json' try: fx.paint.preset = i if len(node.state["preset" + str(i)]) > 0: dic = node.state["preset" + str(i)] ppreset = json.dumps(dic, cls=GenericJSONEncoder) with open(fpath, 'w') as file: file.write(ppreset) print("Saved preset %s @ %s" % (i, fpath)) except: print("Preset %s skipped" % i) if os.path.exists(fpath): os.remove(fpath) print("Old Preset %s removed" % i) # e = sys.exc_info() # print('Error on line {}'.format(sys.exc_info()[-1].tb_lineno), type(e).__name__, e) try: fx.paint.preset = current ## go back to original active preset except: pass elif mode == "load": jsonFiles = glob.glob(paint_presets_path + "/**/*.json", recursive=True) filelist = {} namecollection = [] presetsfound = False if len(jsonFiles) > 0: for f in jsonFiles: name = os.path.basename(f) name = str(name).rsplit("_", 1)[0] namecollection.append(name) namecollection = list(set(namecollection)) presetsfound = True else: resulterror = getInput(title="Error", msg="No presets found") if presetsfound: lista = { "id": "list", "label": "List", "value": namecollection[0], "items": namecollection } result = getInput(fields=[lista]) loadedpresets = [] if result != None: for i in range(0, 10): fx.paint.preset = i try: node.setState("preset" + str(i), None) with open(paint_presets_path + "/" + result["list"] + "/" + result["list"] + "_" + str(i) + '.json') as complex_data: data = complex_data.read() b = json.loads(data, cls=GenericJSONDecoder) for ii in b.keys(): fx.paint.setState(ii, b[ii]) fx.paint.savePreset(i) loadedpresets.append(i) except: pass fx.paint.preset = min( loadedpresets) ## loads the first available preset