diff --git a/pype/ftrack/actions/action_prepare_project.py b/pype/ftrack/actions/action_prepare_project.py index 4cc6cfd8df..9b21febb81 100644 --- a/pype/ftrack/actions/action_prepare_project.py +++ b/pype/ftrack/actions/action_prepare_project.py @@ -1,9 +1,8 @@ import os import json -from ruamel import yaml from pype.ftrack import BaseAction -from pypeapp import config +from pypeapp import config, Anatomy, project_overrides_dir_path from pype.ftrack.lib.avalon_sync import get_avalon_attr @@ -24,6 +23,7 @@ class PrepareProject(BaseAction): # Key to store info about trigerring create folder structure create_project_structure_key = "create_folder_structure" + item_splitter = {'type': 'label', 'value': '---'} def discover(self, session, entities, event): ''' Validation ''' @@ -41,15 +41,190 @@ class PrepareProject(BaseAction): # Inform user that this may take a while self.show_message(event, "Preparing data... Please wait", True) + self.log.debug("Preparing data which will be shown") self.log.debug("Loading custom attributes") - cust_attrs, hier_cust_attrs = get_avalon_attr(session, True) - project_defaults = config.get_presets( - entities[0]["full_name"] - ).get("ftrack", {}).get("project_defaults", {}) - self.log.debug("Preparing data which will be shown") + project_name = entities[0]["full_name"] + + project_defaults = ( + config.get_presets(project_name) + .get("ftrack", {}) + .get("project_defaults", {}) + ) + + anatomy = Anatomy(project_name) + if not anatomy.roots: + return { + "success": False, + "message": ( + "Have issues with loading Roots for project \"{}\"." + ).format(anatomy.project_name) + } + + root_items = self.prepare_root_items(anatomy) + + ca_items, multiselect_enumerators = ( + self.prepare_custom_attribute_items(project_defaults) + ) + + self.log.debug("Heavy items are ready. Preparing last items group.") + + title = "Prepare Project" + items = [] + + # Add root items + items.extend(root_items) + items.append(self.item_splitter) + + # Ask if want to trigger Action Create Folder Structure + items.append({ + "type": "label", + "value": "

Want to create basic Folder Structure?

" + }) + items.append({ + "name": self.create_project_structure_key, + "type": "boolean", + "value": False, + "label": "Check if Yes" + }) + + items.append(self.item_splitter) + items.append({ + "type": "label", + "value": "

Set basic Attributes:

" + }) + + items.extend(ca_items) + + # This item will be last (before enumerators) + # - sets value of auto synchronization + auto_sync_name = "avalon_auto_sync" + auto_sync_item = { + "name": auto_sync_name, + "type": "boolean", + "value": project_defaults.get(auto_sync_name, False), + "label": "AutoSync to Avalon" + } + # Add autosync attribute + items.append(auto_sync_item) + + # Add enumerator items at the end + for item in multiselect_enumerators: + items.append(item) + + return { + "items": items, + "title": title + } + + def prepare_root_items(self, anatomy): + root_items = [] + self.log.debug("Root items preparation begins.") + + root_names = anatomy.root_names() + roots = anatomy.roots + + root_items.append({ + "type": "label", + "value": "

Check your Project root settings

" + }) + root_items.append({ + "type": "label", + "value": ( + "

NOTE: Roots are crutial for path filling" + " (and creating folder structure).

" + ) + }) + root_items.append({ + "type": "label", + "value": ( + "

WARNING: Do not change roots on running project," + " that will cause workflow issues.

" + ) + }) + + default_roots = anatomy.roots + while isinstance(default_roots, dict): + key = tuple(default_roots.keys())[0] + default_roots = default_roots[key] + + empty_text = "Enter root path here..." + + # Root names is None when anatomy templates contain "{root}" + all_platforms = ["windows", "linux", "darwin"] + if root_names is None: + root_items.append(self.item_splitter) + # find first possible key + for platform in all_platforms: + value = default_roots.raw_data.get(platform) or "" + root_items.append({ + "label": platform, + "name": "__root__{}".format(platform), + "type": "text", + "value": value, + "empty_text": empty_text + }) + return root_items + + root_name_data = {} + missing_roots = [] + for root_name in root_names: + root_name_data[root_name] = {} + if not isinstance(roots, dict): + missing_roots.append(root_name) + continue + + root_item = roots.get(root_name) + if not root_item: + missing_roots.append(root_name) + continue + + for platform in all_platforms: + root_name_data[root_name][platform] = ( + root_item.raw_data.get(platform) or "" + ) + + if missing_roots: + default_values = {} + for platform in all_platforms: + default_values[platform] = ( + default_roots.raw_data.get(platform) or "" + ) + + for root_name in missing_roots: + root_name_data[root_name] = default_values + + root_names = list(root_name_data.keys()) + root_items.append({ + "type": "hidden", + "name": "__rootnames__", + "value": json.dumps(root_names) + }) + + for root_name, values in root_name_data.items(): + root_items.append(self.item_splitter) + root_items.append({ + "type": "label", + "value": "Root: \"{}\"".format(root_name) + }) + for platform, value in values.items(): + root_items.append({ + "label": platform, + "name": "__root__{}{}".format(root_name, platform), + "type": "text", + "value": value, + "empty_text": empty_text + }) + + self.log.debug("Root items preparation ended.") + return root_items + + def _attributes_to_set(self, project_defaults): attributes_to_set = {} + + cust_attrs, hier_cust_attrs = get_avalon_attr(self.session, True) + for attr in hier_cust_attrs: key = attr["key"] if key.startswith("avalon_"): @@ -77,45 +252,17 @@ class PrepareProject(BaseAction): attributes_to_set.items(), key=lambda x: x[1]["label"] )) + return attributes_to_set + + def prepare_custom_attribute_items(self, project_defaults): + items = [] + multiselect_enumerators = [] + attributes_to_set = self._attributes_to_set(project_defaults) + self.log.debug("Preparing interface for keys: \"{}\"".format( str([key for key in attributes_to_set]) )) - item_splitter = {'type': 'label', 'value': '---'} - title = "Prepare Project" - items = [] - - # Ask if want to trigger Action Create Folder Structure - items.append({ - "type": "label", - "value": "

Want to create basic Folder Structure?

" - }) - - items.append({ - "name": self.create_project_structure_key, - "type": "boolean", - "value": False, - "label": "Check if Yes" - }) - - items.append(item_splitter) - items.append({ - "type": "label", - "value": "

Set basic Attributes:

" - }) - - multiselect_enumerators = [] - - # This item will be last (before enumerators) - # - sets value of auto synchronization - auto_sync_name = "avalon_auto_sync" - auto_sync_item = { - "name": auto_sync_name, - "type": "boolean", - "value": project_defaults.get(auto_sync_name, False), - "label": "AutoSync to Avalon" - } - for key, in_data in attributes_to_set.items(): attr = in_data["object"] @@ -139,8 +286,7 @@ class PrepareProject(BaseAction): attr_config_data = json.loads(attr_config["data"]) if attr_config["multiSelect"] is True: - multiselect_enumerators.append(item_splitter) - + multiselect_enumerators.append(self.item_splitter) multiselect_enumerators.append({ "type": "label", "value": in_data["label"] @@ -160,10 +306,7 @@ class PrepareProject(BaseAction): "label": "- {}".format(option["menu"]) } if default: - if ( - isinstance(default, list) or - isinstance(default, tuple) - ): + if isinstance(default, (list, tuple)): if name in default: item["value"] = True else: @@ -204,17 +347,7 @@ class PrepareProject(BaseAction): items.append(item) - # Add autosync attribute - items.append(auto_sync_item) - - # Add enumerator items at the end - for item in multiselect_enumerators: - items.append(item) - - return { - 'items': items, - 'title': title - } + return items, multiselect_enumerators def launch(self, session, entities, event): if not event['data'].get('values', {}): @@ -222,6 +355,35 @@ class PrepareProject(BaseAction): in_data = event['data']['values'] + root_values = {} + root_key = "__root__" + for key, value in tuple(in_data.items()): + if key.startswith(root_key): + _key = key[len(root_key):] + root_values[_key] = in_data.pop(key) + + root_names = in_data.pop("__rootnames__", None) + root_data = {} + if root_names: + for root_name in json.loads(root_names): + root_data[root_name] = {} + for key, value in tuple(root_values.items()): + if key.startswith(root_name): + _key = key[len(root_name):] + root_data[root_name][_key] = value + + else: + for key, value in root_values.items(): + root_data[key] = value + + project_name = entities[0]["full_name"] + anatomy = Anatomy(project_name) + anatomy.templates_obj.save_project_overrides(project_name) + anatomy.roots_obj.save_project_overrides( + project_name, root_data, override=True + ) + anatomy.reset() + # pop out info about creating project structure create_proj_struct = in_data.pop(self.create_project_structure_key) @@ -269,94 +431,22 @@ class PrepareProject(BaseAction): def create_project_specific_config(self, project_name, json_data): self.log.debug("*** Creating project specifig configs ***") - - path_proj_configs = os.environ.get('PYPE_PROJECT_CONFIGS', "") - - # Skip if PYPE_PROJECT_CONFIGS is not set - # TODO show user OS message - if not path_proj_configs: - self.log.warning(( - "Environment variable \"PYPE_PROJECT_CONFIGS\" is not set." - " Project specific config can't be set." - )) - return - - path_proj_configs = os.path.normpath(path_proj_configs) - # Skip if path does not exist - # TODO create if not exist?!!! - if not os.path.exists(path_proj_configs): - self.log.warning(( - "Path set in Environment variable \"PYPE_PROJECT_CONFIGS\"" - " Does not exist." - )) - return - - project_specific_path = os.path.normpath( - os.path.join(path_proj_configs, project_name) - ) + project_specific_path = project_overrides_dir_path(project_name) if not os.path.exists(project_specific_path): os.makedirs(project_specific_path) self.log.debug(( "Project specific config folder for project \"{}\" created." ).format(project_name)) - # Anatomy #################################### - self.log.debug("--- Processing Anatomy Begins: ---") - - anatomy_dir = os.path.normpath(os.path.join( - project_specific_path, "anatomy" - )) - anatomy_path = os.path.normpath(os.path.join( - anatomy_dir, "default.yaml" - )) - - anatomy = None - if os.path.exists(anatomy_path): - self.log.debug( - "Anatomy file already exist. Trying to read: \"{}\"".format( - anatomy_path - ) - ) - # Try to load data - with open(anatomy_path, 'r') as file_stream: - try: - anatomy = yaml.load(file_stream, Loader=yaml.loader.Loader) - self.log.debug("Reading Anatomy file was successful") - except yaml.YAMLError as exc: - self.log.warning( - "Reading Yaml file failed: \"{}\"".format(anatomy_path), - exc_info=True - ) - - if not anatomy: - self.log.debug("Anatomy is not set. Duplicating default.") - # Create Anatomy folder - if not os.path.exists(anatomy_dir): - self.log.debug( - "Creating Anatomy folder: \"{}\"".format(anatomy_dir) - ) - os.makedirs(anatomy_dir) - - source_items = [ - os.environ["PYPE_CONFIG"], "anatomy", "default.yaml" - ] - - source_path = os.path.normpath(os.path.join(*source_items)) - with open(source_path, 'r') as file_stream: - source_data = file_stream.read() - - with open(anatomy_path, 'w') as file_stream: - file_stream.write(source_data) - # Presets #################################### self.log.debug("--- Processing Presets Begins: ---") - project_defaults_dir = os.path.normpath(os.path.join(*[ + project_defaults_dir = os.path.normpath(os.path.join( project_specific_path, "presets", "ftrack" - ])) - project_defaults_path = os.path.normpath(os.path.join(*[ + )) + project_defaults_path = os.path.normpath(os.path.join( project_defaults_dir, "project_defaults.json" - ])) + )) # Create folder if not exist if not os.path.exists(project_defaults_dir): self.log.debug("Creating Ftrack Presets folder: \"{}\"".format( @@ -372,5 +462,4 @@ class PrepareProject(BaseAction): def register(session, plugins_presets={}): '''Register plugin. Called when used as an plugin.''' - PrepareProject(session, plugins_presets).register()