def workfiles_tool(self):
        if self._workfiles_tool is not None:
            return self._workfiles_tool

        from openpype.tools.workfiles.app import (Window,
                                                  validate_host_requirements)
        # Host validation
        host = api.registered_host()
        validate_host_requirements(host)

        window = Window()
        window.refresh()
        window.setWindowFlags(window.windowFlags()
                              | QtCore.Qt.WindowStaysOnTopHint)

        # window.setStyleSheet(style.load_stylesheet())

        context = {
            "asset": api.Session["AVALON_ASSET"],
            "silo": api.Session["AVALON_SILO"],
            "task": api.Session["AVALON_TASK"]
        }
        window.set_context(context)

        self._workfiles_tool = window

        return window
Esempio n. 2
0
def check_inventory():
    if not lib.any_outdated():
        return

    host = api.registered_host()
    outdated_containers = []
    for container in host.ls():
        representation = container['representation']
        representation_doc = io.find_one(
            {
                "_id": io.ObjectId(representation),
                "type": "representation"
            },
            projection={"parent": True})
        if representation_doc and not lib.is_latest(representation_doc):
            outdated_containers.append(container)

    # Warn about outdated containers.
    print("Starting new QApplication..")
    app = Qt.QtWidgets.QApplication(sys.argv)

    message_box = Qt.QtWidgets.QMessageBox()
    message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
    msg = "There are outdated containers in the scene."
    message_box.setText(msg)
    message_box.exec_()

    # Garbage collect QApplication.
    del app
Esempio n. 3
0
def get_all_asset_nodes():
    """Get all assets from the scene, container based"""

    host = api.registered_host()

    nodes = list()

    for container in host.ls():
        # We only interested in surface assets !
        # (TODO): This black list should be somewhere else
        if container["loader"] in ("LookLoader", "CameraLoader",
                                   "LightSetLoader"):
            continue

        # Gather all information
        container_name = container["objectName"]
        subset = container["name"]
        namespace = container["namespace"]

        for node in cmds.ls(
                cmds.sets(container_name, query=True, nodesOnly=True)):

            asset_id = get_asset_id(node)
            if asset_id is None:
                continue

            nodes.append({
                "node": node,
                "assetId": asset_id,
                "subset": subset,
                "namespace": namespace,
            })

    return nodes
Esempio n. 4
0
def remove_unused_looks():
    """Removes all loaded looks for which none of the shaders are used.

    This will cleanup all loaded "LookLoader" containers that are unused in
    the current scene.

    """

    host = api.registered_host()

    unused = list()
    for container in host.ls():
        if container['loader'] == "LookLoader":
            members = cmds.sets(container['objectName'], query=True)
            look_sets = cmds.ls(members, type="objectSet")
            for look_set in look_sets:
                # If the set is used than we consider this look *in use*
                if cmds.sets(look_set, query=True):
                    break
            else:
                unused.append(container)

    for container in unused:
        log.info("Removing unused look container: %s", container['objectName'])
        api.remove(container)

    log.info("Finished removing unused looks. (see log for details)")
Esempio n. 5
0
def assign_look_by_version(nodes, version_id):
    """Assign nodes a specific published look version by id.

    This assumes the nodes correspond with the asset.

    Args:
        nodes(list): nodes to assign look to
        version_id (bson.ObjectId): database id of the version

    Returns:
        None
    """

    # Get representations of shader file and relationships
    look_representation = io.find_one({
        "type": "representation",
        "parent": version_id,
        "name": "ma"
    })

    json_representation = io.find_one({
        "type": "representation",
        "parent": version_id,
        "name": "json"
    })

    # See if representation is already loaded, if so reuse it.
    host = api.registered_host()
    representation_id = str(look_representation['_id'])
    for container in host.ls():
        if (container['loader'] == "LookLoader"
                and container['representation'] == representation_id):
            log.info("Reusing loaded look ..")
            container_node = container['objectName']
            break
    else:
        log.info("Using look for the first time ..")

        # Load file
        loaders = api.loaders_from_representation(api.discover(api.Loader),
                                                  representation_id)
        Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None)
        if Loader is None:
            raise RuntimeError("Could not find LookLoader, this is a bug")

        # Reference the look file
        with maya.maintained_selection():
            container_node = pipeline.load(Loader, look_representation)

    # Get container members
    shader_nodes = cmds.sets(container_node, query=True)

    # Load relationships
    shader_relation = api.get_representation_path(json_representation)
    with open(shader_relation, "r") as f:
        relationships = json.load(f)

    # Assign relationships
    apply_shaders(relationships, shader_nodes, nodes)
Esempio n. 6
0
    def from_workfile(self, additional_jobs=None):
        """Generate jobs from workfile (DCC App agnostic)

        Args:
            additional_jobs (list, optional): A list of callbacks

        """
        # Add workfile
        session = api.Session
        host = api.registered_host()
        workfile = host.current_file()
        if workfile is None:
            # Must be saved since we are parsing workfile here
            raise Exception("Could not obtain workfile path.")

        # Compute remote work dir
        project = io.find_one({"type": "project"})
        template = project["config"]["template"]["work"]
        remote_path = template.format(
            **{
                "root": self.remote_root,
                "project": session["AVALON_PROJECT"],
                "silo": session["AVALON_SILO"],
                "asset": session["AVALON_ASSET"],
                "task": session["AVALON_TASK"],
                "app": session["AVALON_APP"],
                "user": self.remote_user,
            })

        workfile_name = os.path.basename(workfile)
        local_user = session.get("AVALON_USER", getpass.getuser())
        # Prevent workfile overwrite when the remote username is not the
        # same as local username. This happens when multiple local users
        # using same remote account to access remote machine.
        same_user = local_user == self.remote_user
        if not same_user and "{user}" in template:
            # Prefix local username into file name
            workfile_name = local_user + "_" + workfile_name

        remote_path += "/scenes/"  # AVALON_SCENEDIR
        remote_path += workfile_name

        workfile = os.path.normpath(workfile)
        remote_path = os.path.normpath(remote_path)

        # Add workfile job
        self.add_job(files=[(workfile, remote_path)],
                     type="Workfile",
                     description="%s - %s" %
                     (session["AVALON_ASSET"], os.path.basename(workfile)))

        # Additional jobs
        for job in additional_jobs or []:
            job()
Esempio n. 7
0
    def parse_containers(self):
        """

        NOTE: This is the additional job for workfile

        """
        def texture_lookup(version_id):
            representations = set()

            dependent = "data.dependents.%s" % version_id
            filter = {
                "type": "version",
                "data.families": "reveries.texture",
                dependent: {
                    "$exists": True
                },
            }
            version = io.find_one(filter)

            if version is not None:
                representation = io.find_one({"parent": version["_id"]})
                representations.add(str(representation["_id"]))
                # Patching textures
                for pre_version in io.find(
                    {
                        "parent": version["parent"],
                        "name": {
                            "$lt": version["name"]
                        }
                    },
                        sort=[("name", -1)]):

                    pre_repr = io.find_one({"parent": pre_version["_id"]})

                    if "fileInventory" in pre_repr["data"]:
                        representations.add(str(pre_repr["_id"]))
                    else:
                        break

            return representations

        # Start

        representations = set()
        versions = set()
        for container in api.registered_host().ls():
            representations.add(container["representation"])
            versions.add(container["versionId"])

        for id in versions:
            representations.update(texture_lookup(id))

        for id in representations:
            self.from_representation(id)
Esempio n. 8
0
def patched_discover(superclass):
    """
    Monkey patched version of :func:`avalon.api.discover()`. It allows
    us to load presets on plugins being discovered.
    """
    # run original discover and get plugins
    plugins = _original_discover(superclass)

    # determine host application to use for finding presets
    if avalon.registered_host() is None:
        return plugins
    host = avalon.registered_host().__name__.split(".")[-1]

    # map plugin superclass to preset json. Currenly suppoted is load and
    # create (avalon.api.Loader and avalon.api.Creator)
    plugin_type = "undefined"
    if superclass.__name__.split(".")[-1] == "Loader":
        plugin_type = "load"
    elif superclass.__name__.split(".")[-1] == "Creator":
        plugin_type = "create"

    print(">>> trying to find presets for {}:{} ...".format(host, plugin_type))
    try:
        config_data = config.get_presets()['plugins'][host][plugin_type]
    except KeyError:
        print("*** no presets found.")
    else:
        for plugin in plugins:
            if plugin.__name__ in config_data:
                print(">>> We have preset for {}".format(plugin.__name__))
                for option, value in config_data[plugin.__name__].items():
                    if option == "enabled" and value is False:
                        setattr(plugin, "active", False)
                        print("  - is disabled by preset")
                    else:
                        setattr(plugin, option, value)
                        print("  - setting `{}`: `{}`".format(option, value))
    return plugins
Esempio n. 9
0
    def export(self, out=None):
        """Write out JSON format job package file

        Args:
            out (str, optional): Output file path

        """
        if out is None:
            host = api.registered_host()
            workfile = host.current_file() or "temp"
            out = os.path.abspath(workfile + ".sftp.job")

        with open(out, "w") as file:
            json.dump(self.jobs, file, indent=4)

        return out
Esempio n. 10
0
def get_containers():
    """Collect all containers in the scene and collect all their nodes

    Returns:
        generator object

    """
    host = api.registered_host()

    for container in host.ls():
        nodes = cmds.sets(container["objectName"], query=True, nodesOnly=True)
        nodes = cmds.ls(nodes, long=True)

        # Update container
        container.update({"nodes": create_id_hash(nodes)})

        yield container
Esempio n. 11
0
def get_all_asset_nodes():
    """Get all assets from the scene, container based"""

    host = api.registered_host()

    nodes = list()

    for container in host.ls():
        # We only interested in surface assets !
        # (TODO): This black list should be somewhere else
        if container["loader"] in ("LookLoader", "CameraLoader",
                                   "ArnoldAssLoader", "SetDressLoader",
                                   "LightSetLoader"):
            continue

        # Gather all information
        subset = container["name"]
        namespace = container["namespace"]

        # (NOTE) Previously, nodes was collecting from container objectSet,
        #        but container may lost it's member nodes during production.
        #        So change to get asset nodes from hierarchy to ensure user
        #        always gets all loaded subsets.
        group = container.get("subsetGroup")
        if not group:
            continue

        for node in cmds.listRelatives(group,
                                       allDescendents=True,
                                       path=True,
                                       type="transform"):

            asset_id = get_asset_id(node)
            if asset_id is None:
                continue

            nodes.append({
                "node": node,
                "assetId": asset_id,
                "subset": subset,
                "namespace": namespace,
            })

    return nodes
Esempio n. 12
0
def get_containers(nodes):
    """Get containers for the nodes

    Args:
        nodes (list): collect of strings, e.g: selected nodes

    Return:
        dict
    """

    host = api.registered_host()
    results = {}
    nodes = set(nodes)
    for container in host.ls():
        container_object = container['objectName']
        members = set(cmds.sets(container_object, query=True) or [])
        if nodes.intersection(members):
            results[container_object] = list(members)

    return results
Esempio n. 13
0
def get_all_asset_nodes():
    """Get all assets from the scene, container based

    Returns:
        list: list of dictionaries
    """

    host = api.registered_host()

    nodes = []
    for container in host.ls():
        # We are not interested in looks but assets!
        if container["loader"] == "LookLoader":
            continue

        # Gather all information
        container_name = container["objectName"]
        nodes += cmds.sets(container_name, query=True, nodesOnly=True) or []

    return nodes
Esempio n. 14
0
def get_all_asset_nodes():
    """Get all assets from the scene, container based

    Returns:
        list: list of dictionaries
    """

    host = api.registered_host()

    nodes = []
    for container in host.ls():
        # We only interested in surface assets !
        # (TODO): This black list should be somewhere else
        if container["loader"] in ("LookLoader", "CameraLoader",
                                   "LightSetLoader"):
            continue

        # Gather all information
        container_name = container["objectName"]
        nodes += cmds.sets(container_name, query=True, nodesOnly=True) or []

    return nodes
Esempio n. 15
0
    def reset(self):
        self.blockSignals(True)
        self.clear()
        self.blockSignals(False)

        host = api.registered_host()
        if host is None:
            return

        self.beginResetModel()

        self.appendRow(self.placeholder_item)

        for container in host.ls():
            if lib.is_supported_loader(container["loader"]):
                item = QtGui.QStandardItem(container["namespace"][1:])

                item.setData(container, QtCore.Qt.UserRole)

                self.appendRow(item)

        self.endResetModel()
Esempio n. 16
0
def show(root=None, debug=False, parent=None, use_context=True, save=True):
    """Show Work Files GUI"""
    # todo: remove `root` argument to show()

    try:
        module.window.close()
        del (module.window)
    except (AttributeError, RuntimeError):
        pass

    host = api.registered_host()
    validate_host_requirements(host)

    if debug:
        api.Session["AVALON_ASSET"] = "Mock"
        api.Session["AVALON_TASK"] = "Testing"

    with tools_lib.application():
        window = Window(parent=parent)
        window.refresh()

        if use_context:
            context = {
                "asset": api.Session["AVALON_ASSET"],
                "silo": api.Session["AVALON_SILO"],
                "task": api.Session["AVALON_TASK"]
            }
            window.set_context(context)

        window.files_widget.btn_save.setEnabled(save)

        window.show()
        window.setStyleSheet(style.load_stylesheet())

        module.window = window

        # Pull window to the front.
        module.window.raise_()
        module.window.activateWindow()
Esempio n. 17
0
    def parse_stray_textures(self):
        """Find file nodes which pointing files that were not in published space

        If there are any texture files that has not been published...

        NOTE: This is the additional job for workfile

        """
        session = api.Session
        host = api.registered_host()
        workfile = host.current_file()
        project = session["AVALON_PROJECT"]

        jobs = list()

        for file_node in find_stray_textures():
            file_path = cmds.getAttr(file_node + ".fileTextureName",
                                     expandEnvironmentVariables=True)

            if project in file_path:
                head, tail = file_path.split(project, 1)
                # Replace root
                remote_path = self.remote_root + os.sep + project + tail

                file_path = os.path.normpath(file_path)
                remote_path = os.path.normpath(remote_path)

                jobs.append((file_path, remote_path))

        if not jobs:
            return

        self.add_job(files=jobs,
                     type="Stray Textures",
                     description="%s - %s" %
                     (session["AVALON_ASSET"], os.path.basename(workfile)))
Esempio n. 18
0
def get_all_assets():
    """Get all assets from the scene

    Returns:
        list
    """

    host = api.registered_host()

    items = []
    for container in host.ls():
        # We are not interested in looks but assets!
        if container["loader"] == "LookLoader":
            continue
        # Gather all information
        container_name = container["objectName"]
        content = cmds.sets(container_name, query=True)
        item = create_item_from_container(container_name, content)
        if not item:
            continue

        items.append(item)

    return items
Esempio n. 19
0
    def __init__(self, parent=None):
        super(FilesWidget, self).__init__(parent=parent)

        # Setup
        self._asset = None
        self._task = None

        # Pype's anatomy object for current project
        self.anatomy = Anatomy(io.Session["AVALON_PROJECT"])
        # Template key used to get work template from anatomy templates
        # TODO change template key based on task
        self.template_key = "work"

        # This is not root but workfile directory
        self.root = None
        self.host = api.registered_host()

        # Whether to automatically select the latest modified
        # file on a refresh of the files model.
        self.auto_select_latest_modified = True

        # Avoid crash in Blender and store the message box
        # (setting parent doesn't work as it hides the message box)
        self._messagebox = None

        files_view = FilesView(self)

        # Create the Files model
        extensions = set(self.host.file_extensions())
        files_model = FilesModel(file_extensions=extensions)

        # Create proxy model for files to be able sort and filter
        proxy_model = QtCore.QSortFilterProxyModel()
        proxy_model.setSourceModel(files_model)
        proxy_model.setDynamicSortFilter(True)
        proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)

        # Set up the file list tree view
        files_view.setModel(proxy_model)
        files_view.setSortingEnabled(True)
        files_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

        # Date modified delegate
        time_delegate = PrettyTimeDelegate()
        files_view.setItemDelegateForColumn(1, time_delegate)
        files_view.setIndentation(3)  # smaller indentation

        # Default to a wider first filename column it is what we mostly care
        # about and the date modified is relatively small anyway.
        files_view.setColumnWidth(0, 330)

        # Filtering input
        filter_input = QtWidgets.QLineEdit(self)
        filter_input.textChanged.connect(proxy_model.setFilterFixedString)
        filter_input.setPlaceholderText("Filter files..")

        # Home Page
        # Build buttons widget for files widget
        btns_widget = QtWidgets.QWidget(self)
        btn_save = QtWidgets.QPushButton("Save As", btns_widget)
        btn_browse = QtWidgets.QPushButton("Browse", btns_widget)
        btn_open = QtWidgets.QPushButton("Open", btns_widget)

        btns_layout = QtWidgets.QHBoxLayout(btns_widget)
        btns_layout.setContentsMargins(0, 0, 0, 0)
        btns_layout.addWidget(btn_open)
        btns_layout.addWidget(btn_browse)
        btns_layout.addWidget(btn_save)

        # Build files widgets for home page
        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.addWidget(filter_input)
        main_layout.addWidget(files_view)
        main_layout.addWidget(btns_widget)

        # Register signal callbacks
        files_view.doubleClickedLeft.connect(self.on_open_pressed)
        files_view.customContextMenuRequested.connect(self.on_context_menu)
        files_view.selectionModel().selectionChanged.connect(
            self.on_file_select)

        btn_open.pressed.connect(self.on_open_pressed)
        btn_browse.pressed.connect(self.on_browse_pressed)
        btn_save.pressed.connect(self.on_save_as_pressed)

        # Store attributes
        self.time_delegate = time_delegate

        self.filter_input = filter_input

        self.files_view = files_view
        self.files_model = files_model

        self.btns_widget = btns_widget
        self.btn_open = btn_open
        self.btn_browse = btn_browse
        self.btn_save = btn_save
Esempio n. 20
0
    def __init__(self, parent, root, anatomy, template_key, session=None):
        super(NameWindow, self).__init__(parent=parent)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)

        self.result = None
        self.host = api.registered_host()
        self.root = root
        self.work_file = None

        if not session:
            # Fallback to active session
            session = api.Session

        # Set work file data for template formatting
        asset_name = session["AVALON_ASSET"]
        project_doc = io.find_one({"type": "project"})
        self.data = {
            "project": {
                "name": project_doc["name"],
                "code": project_doc["data"].get("code")
            },
            "asset": asset_name,
            "task": session["AVALON_TASK"],
            "version": 1,
            "user": getpass.getuser(),
            "comment": "",
            "ext": None
        }

        # Store project anatomy
        self.anatomy = anatomy
        self.template = anatomy.templates[template_key]["file"]
        self.template_key = template_key

        # Btns widget
        btns_widget = QtWidgets.QWidget(self)

        btn_ok = QtWidgets.QPushButton("Ok", btns_widget)
        btn_cancel = QtWidgets.QPushButton("Cancel", btns_widget)

        btns_layout = QtWidgets.QHBoxLayout(btns_widget)
        btns_layout.addWidget(btn_ok)
        btns_layout.addWidget(btn_cancel)

        # Inputs widget
        inputs_widget = QtWidgets.QWidget(self)

        # Version widget
        version_widget = QtWidgets.QWidget(inputs_widget)

        # Version number input
        version_input = QtWidgets.QSpinBox(version_widget)
        version_input.setMinimum(1)
        version_input.setMaximum(9999)

        # Last version checkbox
        last_version_check = QtWidgets.QCheckBox("Next Available Version",
                                                 version_widget)
        last_version_check.setChecked(True)

        version_layout = QtWidgets.QHBoxLayout(version_widget)
        version_layout.setContentsMargins(0, 0, 0, 0)
        version_layout.addWidget(version_input)
        version_layout.addWidget(last_version_check)

        # Preview widget
        preview_label = QtWidgets.QLabel("Preview filename", inputs_widget)

        # Subversion input
        subversion_input = QtWidgets.QLineEdit(inputs_widget)
        subversion_input.setPlaceholderText("Will be part of filename.")

        # Extensions combobox
        ext_combo = QtWidgets.QComboBox(inputs_widget)
        ext_combo.addItems(self.host.file_extensions())

        # Build inputs
        inputs_layout = QtWidgets.QFormLayout(inputs_widget)
        # Add version only if template contain version key
        # - since the version can be padded with "{version:0>4}" we only search
        #   for "{version".
        if "{version" in self.template:
            inputs_layout.addRow("Version:", version_widget)

        # Add subversion only if template containt `{comment}`
        if "{comment}" in self.template:
            inputs_layout.addRow("Subversion:", subversion_input)
        inputs_layout.addRow("Extension:", ext_combo)
        inputs_layout.addRow("Preview:", preview_label)

        # Build layout
        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.addWidget(inputs_widget)
        main_layout.addWidget(btns_widget)

        # Singal callback registration
        version_input.valueChanged.connect(self.on_version_spinbox_changed)
        last_version_check.stateChanged.connect(
            self.on_version_checkbox_changed)

        subversion_input.textChanged.connect(self.on_comment_changed)
        ext_combo.currentIndexChanged.connect(self.on_extension_changed)

        btn_ok.pressed.connect(self.on_ok_pressed)
        btn_cancel.pressed.connect(self.on_cancel_pressed)

        # Allow "Enter" key to accept the save.
        btn_ok.setDefault(True)

        # Force default focus to comment, some hosts didn't automatically
        # apply focus to this line edit (e.g. Houdini)
        subversion_input.setFocus()

        # Store widgets
        self.btn_ok = btn_ok

        self.version_widget = version_widget

        self.version_input = version_input
        self.last_version_check = last_version_check

        self.preview_label = preview_label
        self.subversion_input = subversion_input
        self.ext_combo = ext_combo

        self.refresh()
Esempio n. 21
0
def switch(asset_name, filepath=None, new=True):
    """Switch the current containers of the file to the other asset (shot)

    Args:
        filepath (str): file path of the comp file
        asset_name (str): name of the asset (shot)
        new (bool): Save updated comp under a different name

    Returns:
        comp path (str): new filepath of the updated comp

    """

    # If filepath provided, ensure it is valid absolute path
    if filepath is not None:
        if not os.path.isabs(filepath):
            filepath = os.path.abspath(filepath)

        assert os.path.exists(filepath), "%s must exist " % filepath

    # Assert asset name exists
    # It is better to do this here then to wait till switch_shot does it
    asset = io.find_one({"type": "asset", "name": asset_name})
    assert asset, "Could not find '%s' in the database" % asset_name

    # Get current project
    self._project = io.find_one({
        "type": "project",
        "name": api.Session["AVALON_PROJECT"]
    })

    # Go to comp
    if not filepath:
        current_comp = avalon.fusion.get_current_comp()
        assert current_comp is not None, "Could not find current comp"
    else:
        fusion = _get_fusion_instance()
        current_comp = fusion.LoadComp(filepath, quiet=True)
        assert current_comp is not None, "Fusion could not load '%s'" % filepath

    host = api.registered_host()
    containers = list(host.ls())
    assert containers, "Nothing to update"

    representations = []
    for container in containers:
        try:
            representation = pype.switch_item(container, asset_name=asset_name)
            representations.append(representation)
        except Exception as e:
            current_comp.Print("Error in switching! %s\n" % e.message)

    message = "Switched %i Loaders of the %i\n" % (len(representations),
                                                   len(containers))
    current_comp.Print(message)

    # Build the session to switch to
    switch_to_session = api.Session.copy()
    switch_to_session["AVALON_ASSET"] = asset['name']

    if new:
        comp_path = _format_filepath(switch_to_session)

        # Update savers output based on new session
        _update_savers(current_comp, switch_to_session)
    else:
        comp_path = pype.version_up(filepath)

    current_comp.Print(comp_path)

    current_comp.Print("\nUpdating frame range")
    update_frame_range(current_comp, representations)

    current_comp.Save(comp_path)

    return comp_path
Esempio n. 22
0
    def refresh(self):
        model_containers = list()

        host = api.registered_host()
        for container in host.ls():
            if container["loader"] == "ModelLoader":
                model_containers.append(container)

        self.clear()
        self.beginResetModel()

        for container in model_containers:

            subset_id = io.ObjectId(container["subsetId"])
            version_id = io.ObjectId(container["versionId"])
            version = io.find_one({"_id": version_id})
            latest = io.find_one({
                "type": "version",
                "parent": subset_id
            },
                                 sort=[("name", -1)],
                                 projection={"name": True})
            latest_repr = io.find_one({
                "type": "representation",
                "parent": latest["_id"],
                "name": "mayaBinary"
            })

            # Is latest version loaded ?
            is_latest = latest["name"] == version["name"]

            versions = io.find({
                "type": "version",
                "parent": subset_id
            },
                               sort=[("name", -1)])
            for version in versions:
                repr = io.find_one({
                    "type": "representation",
                    "parent": version["_id"],
                    "name": "mayaBinary"
                })

                protected = repr["data"].get("modelProtected")
                if protected is not None:
                    # Get protected list from previous version if not found
                    break
            protected = protected or set()

            namespace = container["namespace"]
            subset_group = container["subsetGroup"]
            subset_item = models.Item()
            subset_item.update({
                "subsetId":
                subset_id,
                "representation":
                latest_repr,
                "namespace":
                namespace,
                "node":
                subset_group,
                "name":
                subset_group.rsplit("|", 1)[-1][len(namespace):],
                "isLatest":
                is_latest,
            })

            members = cmds.sets(container["objectName"], query=True)
            for node in cmds.ls(members,
                                type="transform",
                                referencedNodes=True,
                                long=True):
                meshes = cmds.listRelatives(node,
                                            shapes=True,
                                            noIntermediate=True,
                                            type="mesh")
                if not meshes:
                    continue

                id = utils.get_id(node)
                is_locked = id in protected

                node_item = models.Item()
                node_item.update({
                    "node":
                    node,
                    "name":
                    node.rsplit("|", 1)[-1][len(namespace):],
                    "avalonId":
                    id,
                    "isLocked":
                    is_locked,
                    "isLatest":
                    is_latest,
                    "setLocked":
                    None,
                })

                subset_item.add_child(node_item)

            self.add_child(subset_item)

        self.endResetModel()