diff --git a/client/ayon_core/hosts/zbrush/api/lib.py b/client/ayon_core/hosts/zbrush/api/lib.py index 7b288008e4..1020575795 100644 --- a/client/ayon_core/hosts/zbrush/api/lib.py +++ b/client/ayon_core/hosts/zbrush/api/lib.py @@ -3,14 +3,8 @@ import uuid import time import tempfile import logging -from ayon_core.client import ( - get_project, - get_asset_by_name, -) -from ayon_core.pipeline import Anatomy -from string import Formatter + from . import CommunicationWrapper -from ayon_core.pipeline.template_data import get_template_data log = logging.getLogger("zbrush.lib") @@ -46,7 +40,6 @@ def execute_zscript(zscript, communicator=None): def execute_zscript_and_wait(zscript, check_filepath=None, - sub_level=0, wait=0.1, timeout=20): """Execute ZScript and wait until ZScript finished processing. @@ -88,81 +81,49 @@ def execute_zscript_and_wait(zscript, ) -def find_first_filled_path(path): - if not path: - return "" - - fields = set() - for item in Formatter().parse(path): - _, field_name, format_spec, conversion = item - if not field_name: - continue - conversion = "!{}".format(conversion) if conversion else "" - format_spec = ":{}".format(format_spec) if format_spec else "" - orig_key = "{{{}{}{}}}".format( - field_name, conversion, format_spec) - fields.add(orig_key) - - for field in fields: - path = path.split(field, 1)[0] - return path +def get_workdir() -> str: + """Return the currently active work directory""" + return os.environ["AYON_WORKDIR"] -def get_workdir(project_name, asset_name, task_name): - project = get_project(project_name) - asset = get_asset_by_name(project_name, asset_name) +def export_tool(filepath: str, subdivision_level: int = 0): + """Export active zbrush tool to filepath. - data = get_template_data(project, asset, task_name) + Args: + filepath (str): The filepath to export to. + subdivision_level (int): The subdivision level to export. + A value of zero will export the current subdivision level + A negative value, e.g. -1 will go negatively from the highest + subdivs - e.g. -1 is the highest available subdiv. - anatomy = Anatomy(project_name) - workdir = anatomy.templates_obj["work"]["folder"].format(data) - - # Remove any potential un-formatted parts of the path - valid_workdir = find_first_filled_path(workdir) - - # Path is not filled at all - if not valid_workdir: - raise AssertionError("Failed to calculate workdir.") - - # Normalize - valid_workdir = os.path.normpath(valid_workdir) - if os.path.exists(valid_workdir): - return valid_workdir - - data.pop("task", None) - workdir = anatomy.templates_obj["work"]["folder"].format(data) - valid_workdir = find_first_filled_path(workdir) - if valid_workdir: - # Normalize - valid_workdir = os.path.normpath(valid_workdir) - if os.path.exists(valid_workdir): - return valid_workdir - - -def export_tool(filepath: str, sub_level: int): - """Export active zbrush tool to filepath.""" + """ filepath = filepath.replace("\\", "/") - export_tool_zscript = (""" -[IFreeze, -[VarSet, subdlevel, {sub_level}] -[VarSet, maxSubd, [IGetMax, Tool:Geometry:SDiv]] -[If, subdlevel == 0 || sublevel > maxSubd, -[VarSet, subdlevel, maxSubd]] -[ISet, "Tool:Geometry:SDiv", subdlevel, 0] + + # Only set any subdiv level if subdiv level != 0 + set_subdivs_script = "" + if subdivision_level != 0: + set_subdivs_script = f""" +[VarSet, max_subd, [IGetMax, "Tool:Geometry:SDiv"]] +[If, max_subd > 0, + [ISet, "Tool:Geometry:SDiv", {subdivision_level}, 0], + [ISet, "Tool:Geometry:SDiv", [VarGet, max_subd] - {subdivision_level}, 0] +]""" + + # Export tool + export_tool_zscript = f""" +[IFreeze, {set_subdivs_script} [FileNameSetNext, "{filepath}"] [IKeyPress, 13, [IPress, Tool:Export]] -] -""").format(filepath=filepath, sub_level=sub_level) + +]""" # We do not check for the export file's existence because Zbrush might # write the file in chunks, as such the file might exist before the writing # to it has finished - execute_zscript_and_wait( - export_tool_zscript, check_filepath=filepath, - sub_level=sub_level) + execute_zscript_and_wait(export_tool_zscript, check_filepath=filepath) -def is_in_edit_mode(): +def is_in_edit_mode() -> bool: """Return whether transform edit mode is currently enabled. Certain actions can't be performed if Zbrush is currently not within @@ -188,7 +149,7 @@ def is_in_edit_mode(): content = str(mode.read()) bool_mode = content.rstrip('\x00') - return bool_mode + return bool(int(bool_mode)) def remove_subtool(basename): diff --git a/client/ayon_core/hosts/zbrush/api/pipeline.py b/client/ayon_core/hosts/zbrush/api/pipeline.py index ba6d2b597d..13c0a2edda 100644 --- a/client/ayon_core/hosts/zbrush/api/pipeline.py +++ b/client/ayon_core/hosts/zbrush/api/pipeline.py @@ -53,7 +53,6 @@ class ZbrushHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): register_event_callback("application.launched", self.initial_app_launch) register_event_callback("application.exit", self.application_exit) - def get_current_project_name(self): """ Returns: @@ -90,19 +89,16 @@ class ZbrushHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): "folder_path": context.get("folder_path"), "task_name": context.get("task") } + # --- Workfile --- def open_workfile(self, filepath): - open_file_zscript = (""" + filepath = filepath.replace("\\", "/") + execute_zscript(f""" [IFreeze, -[MemCreate, currentfile, 1000, 0] -[VarSet, filename, "{filepath}"] -[MemWriteString, currentfile, #filename, 0] -[FileNameSetNext, #filename] -[IKeyPress, 13, [IPress, File:Open:Open]]] -[MemDelete, currentfile] + [FileNameSetNext, "{filepath}"] + [IKeyPress, 13, [IPress, File:Open:Open]]] ] - """).format(filepath=filepath) - execute_zscript(open_file_zscript) + """) set_current_file(filepath=filepath) return filepath @@ -110,31 +106,23 @@ class ZbrushHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): if not filepath: filepath = self.get_current_workfile() filepath = filepath.replace("\\", "/") - save_file_zscript = (""" -[IFreeze, -[MemCreate, currentfile, 1000, 0] -[VarSet, filename, "{filepath}"] -[MemWriteString, currentfile, #filename, 0] -[FileNameSetNext, #filename] -[IKeyPress, 13, [IPress, File:SaveAs:SaveAs]]] -[MemDelete, currentfile] -] -""").format(filepath=filepath) # # move the json data to the files # # shutil.copy copy_ayon_data(filepath) set_current_file(filepath=filepath) - execute_zscript(save_file_zscript) + execute_zscript(f""" +[IFreeze, + [FileNameSetNext, "{filepath}"] + [IKeyPress, 13, [IPress, File:SaveAs:SaveAs]]] +] +""") return filepath def work_root(self, session): return session["AYON_WORKDIR"] def get_current_workfile(self): - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() txt_dir = os.path.join( work_dir, ".zbrush_metadata").replace( "\\", "/" @@ -194,6 +182,7 @@ class ZbrushHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def get_context_data(self): get_load_workfile_metadata(ZBRUSH_METADATA_CREATE_CONTEXT) + def containerise( name, context, namespace="", loader=None, containers=None): data = { @@ -218,10 +207,7 @@ def save_current_workfile_context(context): def write_context_metadata(metadata_key, context): - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", metadata_key).replace( "\\", "/" @@ -242,14 +228,11 @@ def write_context_metadata(metadata_key, context): def write_workfile_metadata(metadata_key, data=None): if data is None: data = [] - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", current_file, metadata_key).replace( @@ -274,31 +257,6 @@ def get_current_context(): } -def get_workfile_metadata(metadata_key, default=None): - if default is None: - default = [] - output_file = tempfile.NamedTemporaryFile( - mode="w", prefix="a_zb_meta", suffix=".txt", delete=False - ) - output_file.close() - output_filepath = output_file.name.replace("\\", "/") - context_data_zscript = (""" -[IFreeze, -[If, [MemCreate, {metadata_key}, 400000, 0] !=-1, -[MemCreate, {metadata_key}, 400000, 0] -[MemWriteString, {metadata_key}, "{default}", 0]] -[MemSaveToFile, {metadata_key}, "{output_filepath}", 1] -[MemDelete, {metadata_key}] -] -""").format(metadata_key=metadata_key, - default=default, output_filepath=output_filepath) - execute_zscript(context_data_zscript) - with open(output_filepath) as data: - file_content = str(data.read().strip()).rstrip('\x00') - file_content = ast.literal_eval(file_content) - return file_content - - def get_containers(): output = get_load_workfile_metadata(ZBRUSH_SECTION_NAME_CONTAINERS) if output: @@ -311,16 +269,14 @@ def get_containers(): return output + def write_load_metadata(metadata_key, data): #TODO: create temp json file - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() name = next((d["name"] for d in data), None) json_dir = os.path.join( work_dir, ".zbrush_metadata", @@ -339,10 +295,7 @@ def write_load_metadata(metadata_key, data): def get_load_context_metadata(metadata_key): file_content = [] - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", metadata_key).replace( "\\", "/" @@ -363,14 +316,11 @@ def get_load_workfile_metadata(metadata_key): # save zscript to the hidden folder # load json files file_content = [] - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", current_file, metadata_key).replace( @@ -393,14 +343,11 @@ def get_instance_workfile_metadata(metadata_key): # save zscript to the hidden folder # load json files file_content = [] - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", current_file, metadata_key).replace( @@ -416,14 +363,11 @@ def get_instance_workfile_metadata(metadata_key): def remove_container_data(name): - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", current_file, ZBRUSH_SECTION_NAME_CONTAINERS).replace( @@ -437,10 +381,7 @@ def remove_container_data(name): def remove_tmp_data(): - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() for name in [ZBRUSH_METADATA_CREATE_CONTEXT, ZBRUSH_SECTION_NAME_INSTANCES, ZBRUSH_SECTION_NAME_CONTAINERS]: @@ -458,14 +399,11 @@ def remove_tmp_data(): def copy_ayon_data(filepath): filename = os.path.splitext(os.path.basename(filepath))[0].strip() - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() for name in [ZBRUSH_METADATA_CREATE_CONTEXT, ZBRUSH_SECTION_NAME_INSTANCES, ZBRUSH_SECTION_NAME_CONTAINERS]: @@ -490,10 +428,7 @@ def copy_ayon_data(filepath): def set_current_file(filepath=None): - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() txt_dir = os.path.join( work_dir, ".zbrush_metadata").replace( "\\", "/" @@ -516,14 +451,11 @@ def imprint(container, representation_id): old_container_data = [] data = {} name = container["objectName"] - project_name = get_current_context()["project_name"] - folder_path = get_current_context()["folder_path"] - task_name = get_current_context()["task_name"] current_file = registered_host().get_current_workfile() if current_file: current_file = os.path.splitext( os.path.basename(current_file))[0].strip() - work_dir = get_workdir(project_name, folder_path, task_name) + work_dir = get_workdir() json_dir = os.path.join( work_dir, ".zbrush_metadata", current_file, ZBRUSH_SECTION_NAME_CONTAINERS).replace( @@ -546,6 +478,7 @@ def imprint(container, representation_id): file.write(new_container_data) file.close() + def tmp_current_file_check(): output_file = tempfile.NamedTemporaryFile( mode="w", prefix="a_zb_cfc", suffix=".txt", delete=False diff --git a/client/ayon_core/hosts/zbrush/plugins/create/create_model.py b/client/ayon_core/hosts/zbrush/plugins/create/create_model.py index 8e132aed18..348e519e14 100644 --- a/client/ayon_core/hosts/zbrush/plugins/create/create_model.py +++ b/client/ayon_core/hosts/zbrush/plugins/create/create_model.py @@ -9,7 +9,7 @@ class CreateModel(plugin.ZbrushCreator): identifier = "io.ayon.creators.zbrush.model" label = "Model" product_type = "model" - icon = "gear" + icon = "cube" def create(self, product_name, instance_data, pre_create_data): creator_attributes = instance_data.setdefault( @@ -25,11 +25,19 @@ class CreateModel(plugin.ZbrushCreator): def get_instance_attr_defs(self): return [ - NumberDef("subd_level", - label="Subdivision Level", - decimals=0, - minimum=0, - default=0) + NumberDef( + "subd_level", + label="Subdivision Level", + decimals=0, + minimum=-10, + default=0, + tooltip=( + "A level of 1 or higher sets the level to export at.\n" + "A level of 0 means 'Use tool's current subdiv level'.\n" + "A level of -1 or lower subtracts from the highest subdiv," + "\n for example -1 means highest subdiv level." + ) + ) ] def get_pre_create_attr_defs(self):