From ccb4371641b79275702bc5557fefdf3c8d39c0a6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 7 Jan 2023 17:42:43 +0100 Subject: [PATCH] Refactor metadata code to allow more structure for future Substance Painter plugins --- .../hosts/substancepainter/api/pipeline.py | 54 ++++++++++++++++- .../plugins/create/create_workfile.py | 27 ++++----- .../plugins/load/load_mesh.py | 58 +++++++++---------- 3 files changed, 91 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/substancepainter/api/pipeline.py b/openpype/hosts/substancepainter/api/pipeline.py index 4d49fa83d7..e7dbe5e5eb 100644 --- a/openpype/hosts/substancepainter/api/pipeline.py +++ b/openpype/hosts/substancepainter/api/pipeline.py @@ -36,6 +36,10 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + +OPENPYPE_METADATA_KEY = "OpenPype" +OPENPYPE_METADATA_CONTAINERS_KEY = "containers" # child key + self = sys.modules[__name__] self.menu = None self.callbacks = [] @@ -127,8 +131,8 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): if not substance_painter.project.is_open(): return - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) if containers: for key, container in containers.items(): container["objectName"] = key @@ -275,3 +279,49 @@ def imprint_container(container, ] for key, value in data: container[key] = value + + +def set_project_metadata(key, data): + """Set a key in project's OpenPype metadata.""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + metadata.set(key, data) + + +def get_project_metadata(key): + """Get a key from project's OpenPype metadata.""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + return metadata.get(key) + + +def set_container_metadata(object_name, container_data, update=False): + """Helper method to directly set the data for a specific container + + Args: + object_name (str): The unique object name identifier for the container + container_data (dict): The data for the container. + Note 'objectName' data is derived from `object_name` and key in + `container_data` will be ignored. + update (bool): Whether to only update the dict data. + + """ + # The objectName is derived from the key in the metadata so won't be stored + # in the metadata in the container's data. + container_data.pop("objectName", None) + + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) or {} + if update: + existing_data = containers.setdefault(object_name, {}) + existing_data.update(container_data) # mutable dict, in-place update + else: + containers[object_name] = container_data + metadata.set("containers", containers) + + +def remove_container_metadata(object_name): + """Helper method to remove the data for a specific container""" + metadata = substance_painter.project.Metadata(OPENPYPE_METADATA_KEY) + containers = metadata.get(OPENPYPE_METADATA_CONTAINERS_KEY) + if containers: + containers.pop(object_name, None) + metadata.set("containers", containers) diff --git a/openpype/hosts/substancepainter/plugins/create/create_workfile.py b/openpype/hosts/substancepainter/plugins/create/create_workfile.py index cec760040b..8b010ebe2c 100644 --- a/openpype/hosts/substancepainter/plugins/create/create_workfile.py +++ b/openpype/hosts/substancepainter/plugins/create/create_workfile.py @@ -5,20 +5,10 @@ from openpype.pipeline import CreatedInstance, AutoCreator from openpype.pipeline import legacy_io from openpype.client import get_asset_by_name -import substance_painter.project - - -def set_workfile_data(data, update=False): - if update: - data = get_workfile_data().update(data) - metadata = substance_painter.project.Metadata("OpenPype") - metadata.set("workfile", data) - - -def get_workfile_data(): - metadata = substance_painter.project.Metadata("OpenPype") - return metadata.get("workfile") or {} - +from openpype.hosts.substancepainter.api.pipeline import ( + set_project_metadata, + get_project_metadata +) class CreateWorkfile(AutoCreator): """Workfile auto-creator.""" @@ -71,17 +61,20 @@ class CreateWorkfile(AutoCreator): current_instance["task"] = task_name current_instance["subset"] = subset_name - set_workfile_data(current_instance.data_to_store()) + set_project_metadata("workfile", current_instance.data_to_store()) def collect_instances(self): - workfile = get_workfile_data() + workfile = get_project_metadata("workfile") if not workfile: return self.create_instance_in_context_from_existing(workfile) def update_instances(self, update_list): for instance, _changes in update_list: - set_workfile_data(instance.data_to_store(), update=True) + # Update project's workfile metadata + data = get_project_metadata("workfile") or {} + data.update(instance.data_to_store()) + set_project_metadata("workfile", data) # Helper methods (this might get moved into Creator class) def create_instance_in_context(self, subset_name, data): diff --git a/openpype/hosts/substancepainter/plugins/load/load_mesh.py b/openpype/hosts/substancepainter/plugins/load/load_mesh.py index 519ed3ad4e..3e62b90988 100644 --- a/openpype/hosts/substancepainter/plugins/load/load_mesh.py +++ b/openpype/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,27 +2,16 @@ from openpype.pipeline import ( load, get_representation_path, ) -from openpype.hosts.substancepainter.api.pipeline import imprint_container +from openpype.hosts.substancepainter.api.pipeline import ( + imprint_container, + set_container_metadata, + remove_container_metadata +) import substance_painter.project import qargparse -def set_container(key, container): - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") or {} - containers[key] = container - metadata.set("containers", containers) - - -def remove_container(key): - metadata = substance_painter.project.Metadata("OpenPype") - containers = metadata.get("containers") - if containers: - containers.pop(key, None) - metadata.set("containers", containers) - - class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -48,10 +37,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) ] - container_key = "ProjectMesh" - def load(self, context, name, namespace, data): + # Get user inputs + import_cameras = data.get("import_cameras", True) + preserve_strokes = data.get("preserve_strokes", True) + if not substance_painter.project.is_open(): # Allow to 'initialize' a new project # TODO: preferably these settings would come from the actual @@ -59,7 +50,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # visually similar to still allow artist decisions) settings = substance_painter.project.Settings( default_texture_resolution=4096, - import_cameras=data.get("import_cameras", True), + import_cameras=import_cameras, ) substance_painter.project.create( @@ -70,8 +61,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): else: # Reload the mesh settings = substance_painter.project.MeshReloadingSettings( - import_cameras=data.get("import_cameras", True), - preserve_strokes=data.get("preserve_strokes", True) + import_cameras=import_cameras, + preserve_strokes=preserve_strokes ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa @@ -87,13 +78,21 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Store container container = {} + project_mesh_object_name = "_ProjectMesh_" imprint_container(container, - name=self.container_key, - namespace=self.container_key, + name=project_mesh_object_name, + namespace=project_mesh_object_name, context=context, loader=self) - container["options"] = data - set_container(self.container_key, container) + + # We want store some options for updating to keep consistent behavior + # from the user's original choice. We don't store 'preserve_strokes' + # as we always preserve strokes on updates. + container["options"] = { + "import_cameras": import_cameras, + } + + set_container_metadata(project_mesh_object_name, container) def switch(self, container, representation): self.update(container, representation) @@ -107,7 +106,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): container_options = container.get("options", {}) settings = substance_painter.project.MeshReloadingSettings( import_cameras=container_options.get("import_cameras", True), - preserve_strokes=container_options.get("preserve_strokes", True) + preserve_strokes=True ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): @@ -119,8 +118,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): substance_painter.project.reload_mesh(path, settings, on_mesh_reload) # Update container representation - container["representation"] = str(representation["_id"]) - set_container(self.container_key, container) + object_name = container["objectName"] + update_data = {"representation": str(representation["_id"])} + set_container_metadata(object_name, update_data, update=True) def remove(self, container): @@ -128,4 +128,4 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # or close the project? # TODO: This is likely best 'hidden' away to the user because # this will leave the project's mesh unmanaged. - remove_container(self.container_key) + remove_container_metadata(container["objectName"])