Merge pull request #169 from BigRoy/feature/OP-4672_Zbrush-integration

Feature/op 4672 zbrush integration
This commit is contained in:
Kayla Man 2024-03-12 17:07:07 +08:00 committed by GitHub
commit a58e33e3c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 171 deletions

View file

@ -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):

View file

@ -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

View file

@ -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):