Skip to content

validate_usd_look_material_defs

ValidateLookShaderDefs

Bases: HoudiniInstancePlugin, OptionalPyblishPluginMixin

Validate Material primitives are defined types instead of overs

Source code in client/ayon_houdini/plugins/publish/validate_usd_look_material_defs.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class ValidateLookShaderDefs(plugin.HoudiniInstancePlugin,
                             OptionalPyblishPluginMixin):
    """Validate Material primitives are defined types instead of overs"""

    order = pyblish.api.ValidatorOrder
    families = ["look"]
    hosts = ["houdini"]
    label = "Validate Look Shaders Are Defined"
    actions = [SelectROPAction]
    optional = True

    # Types to validate at the low-level Sdf API
    # For Usd API we validate directly against `UsdShade.Material`
    validate_types = [
        "UsdShadeMaterial"
    ]

    def process(self, instance):
        if not self.is_active(instance.data):
            return

        # Get Sdf.Layers from "Collect ROP Sdf Layers and USD Stage" plug-in
        layers = instance.data.get("layers")
        if not layers:
            return
        stage: Usd.Stage = instance.data["stage"]
        layers: List[Sdf.Layer]

        # The Sdf.PrimSpec type name will not have knowledge about inherited
        # types for the type, name. So we pre-collect all invalid types
        # and their child types to ensure we match inherited types as well.
        validate_type_names = set()
        for type_name in self.validate_types:
            validate_type_names.update(get_schema_type_names(type_name))

        invalid = []
        for layer in layers:
            def log_overs(path: Sdf.Path):
                if not path.IsPrimPath():
                    return
                prim_spec = layer.GetPrimAtPath(path)

                if not prim_spec.typeName:
                    # Typeless may mean Houdini generated the material or
                    # shader as override because upstream the nodes already
                    # existed. So we check the stage instead to identify
                    # the composed type of the prim
                    prim = stage.GetPrimAtPath(path)
                    if not prim:
                        return

                    if not prim.IsA(UsdShade.Material):
                        return

                    self.log.debug("Material Prim has no type defined: %s",
                                   path)

                elif prim_spec.typeName not in validate_type_names:
                    return

                if prim_spec.specifier != Sdf.SpecifierDef:
                    specifier = {
                        Sdf.SpecifierDef: "Def",
                        Sdf.SpecifierOver: "Over",
                        Sdf.SpecifierClass: "Class"
                    }[prim_spec.specifier]

                    self.log.warning(
                        "Material is not defined but specified as "
                        "'%s': %s", specifier, path
                    )
                    invalid.append(path)

            layer.Traverse("/", log_overs)

        if invalid:
            raise PublishValidationError(
                "Found Materials not specifying an authored definition.",
                title="Materials not defined",
                description=self.get_description()
            )

    @staticmethod
    def get_description():
        return inspect.cleandoc(
            """### Materials are not defined types

            There are materials in your current look that do not **define** the
            material primitives, but rather **override** or specify a
            **class**. This is most likely not what you want since you want
            most looks to define new materials instead of overriding existing
            materials.

            Usually this happens if your current scene loads an input asset
            that already has the materials you're creating in your current
            scene as well. For example, if you are loading the Asset that
            contains the previously publish of your look without muting the
            look layer. As such, Houdini sees the materials already exist and
            will not make new definitions, but only write "override changes".
            However, once your look publish would replace the previous one then
            suddenly the materials would be missing and only specified as
            overrides.

            So, in most cases this is solved by Layer Muting upstream the
            look layers of the loaded asset.

            If for a specific case the materials already existing in the input
            is correct then you can either specify new material names for what
            you're creating in the current scene or disable this validation
            if you are sure you want to write overrides in your look publish
            instead of definitions.
            """
        )