I am looking to procedurally populate a series of shots (an entire sequence) with workfiles for artists. For each shot I would like there to be a v001 workfile in the directory which already has the assets loaded and mocap data applied (I have the code to create them). I just need to save a workfile for them. What is the best way to do that?
For example, this code (in maya) will iterate over each of the shots for the sequence, and create a sphere. How do I save that sphere to being the first workfile for each shot (animation/body task)?
import sgtk
def all_shots(sequence_id=1234):
e = sgtk.platform.current_engine()
p = e.context.project
sequence = { 'type': 'Sequence', 'id': sequence_id }
return e.shotgun.find("Shot", [('sg_sequence', 'is', sequence), ('project', 'is', p)], ['code'])
def make_shot():
cmds.file(f=True, new=True)
cmds.polySphere()
for shot in all_shots():
make_shot()
save_maya_shot_workfile(shot) ?????
This is a great question. You would use the sgtk API to resolve the Maya work template into a path for each given context.
I am working on a guide that will explain how to do this, but it’s not ready to be properly shared yet. That said I do have a build of the docs which I will message to you directly.
For the benefit of everyone else I’ll post a snippet of code here.
This code will generate a path for a template given a context.
# Initialization
# ==============
import sgtk
import os
# Get the engine instance that is currently running.
current_engine = sgtk.platform.current_engine()
# Grab the pre created Sgtk instance from the current engine.
tk = current_engine.sgtk
# Get a context object from a Task, this Task must belong to a Shot for the future steps to work.
context = tk.context_from_entity("Task", 13155)
# Create the required folders based upon the task
tk.create_filesystem_structure("Task", context.task["id"])
# Generating a Path
# =================
# Get a template instance by providing a name of a valid template in your config's templates.yml
template = tk.templates["maya_shot_publish"]
# Use the context to resolve as many of the template fields as possible.
fields = context.as_template_fields(template)
# Manually resolve the remaining fields that can't be figured out automatically from context.
fields["name"] = "myscene"
# Get an authenticated Shotgun API instance from the engine
sg = current_engine.shotgun
# Run a Shotgun API query to summarize the maximum version number on PublishedFiles that
# are linked to the task and match the provided name.
# Since PublishedFiles generated by the Publish app have the extension on the end of the name we need to add the
# extension in our filter.
r = sg.summarize(entity_type="PublishedFile",
filters = [["task", "is", {"type":"Task", "id": context.task["id"]}],
["name","is", fields["name"] + ".ma"]],
summary_fields=[{"field":"version_number", "type":"maximum"}])
# Extract the version number and add 1 to it.
# In scenarios where there are no files already this summary will return 0.
fields["version"] = r["summaries"]["version_number"] + 1
# Use the fields to resolve the template path into an absolute path.
publish_path = template.apply_fields(fields)
# Make sure we create any missing folders
current_engine.ensure_folder_exists(os.path.dirname(publish_path))
One question… The sg.summarize call will return the highest published version, but I am looking to save a workfile. There may be workfile versions already saved (but not yet published). So presumably I would want the next workfile number, not the next publish number?
Something like this perhaps…?
def workfile(task_id, name=None, version=1):
if name is None: name = "scene"
tk = sgtk.platform.current_engine().sgtk
context = tk.context_from_entity("Task", task_id)
template = tk.templates['maya_shot_work']
fields = context.as_template_fields(template)
fields["name"] = name
fields['version'] = version
return template.apply_fields(fields)
def next_workfile(task_id, name=None):
i = 1
while os.path.isfile(workfile(task_id, name, version=i)):
i+=1
continue
return workfile(task_id, name, version=i)
Thats also covered in the guide as well, although I chose not to make it part of the final example.
Here is a method that should do what you’re after.
def get_next_version_number(tk, template_name, fields):
template_work = tk.templates[template_name]
# Get a list of existing file paths on disk that match the template and provided fields
# Skip the version field as we want to find all versions not a specific version.
skip_fields = ["version"]
work_file_paths = tk.paths_from_template(
template_work,
fields,
skip_fields,
skip_missing_optional_keys=True
)
versions = []
for work_file in work_file_paths:
# extract the values from the path so we can read the version.
path_fields = template_work.get_fields(work_file)
versions.append(path_fields["version"])
# find the highest version in the list and add one.
return max(versions) + 1
get_next_version_number(tk, "maya_shot_work", fields)
Sorry yes I was just reading that bit now. This is what I’m using (based on your doc)
def tk():
return sgtk.platform.current_engine().sgtk
def next_workfile(task_id, name=None, template_name='maya_shot_work'):
if name is None: name = "scene"
template = tk().templates[template_name]
context = tk().context_from_entity("Task", task_id)
fields = context.as_template_fields(template)
fields["name"] = name
fields['version'] = 0
# Get a list of existing file paths on disk that match the template and provided fields
# Skip the version field as we want to find all versions not a specific version.
skip_fields = ["version"]
work_file_paths = tk().paths_from_template(template, fields, skip_fields, skip_missing_optional_keys=True)
versions = []
for work_file in work_file_paths:
# extract the values from the path so we can read the version.
path_fields = template.get_fields(work_file)
versions.append(path_fields["version"])
# find the highest version in the list and add one.
fields['version'] = max(versions) + 1
return template.apply_fields(fields)