Merge pull request #5481 from ynput/tests/publish_process

This commit is contained in:
Ondřej Samohel 2023-09-21 15:40:09 +02:00 committed by GitHub
commit da3f1efaca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 929 additions and 3 deletions

View file

@ -651,6 +651,57 @@ def get_color_management_preferences():
}
def get_obj_node_output(obj_node):
"""Find output node.
If the node has any output node return the
output node with the minimum `outputidx`.
When no output is present return the node
with the display flag set. If no output node is
detected then None is returned.
Arguments:
node (hou.Node): The node to retrieve a single
the output node for.
Returns:
Optional[hou.Node]: The child output node.
"""
outputs = obj_node.subnetOutputs()
if not outputs:
return
elif len(outputs) == 1:
return outputs[0]
else:
return min(outputs,
key=lambda node: node.evalParm('outputidx'))
def get_output_children(output_node, include_sops=True):
"""Recursively return a list of all output nodes
contained in this node including this node.
It works in a similar manner to output_node.allNodes().
"""
out_list = [output_node]
if output_node.childTypeCategory() == hou.objNodeTypeCategory():
for child in output_node.children():
out_list += get_output_children(child, include_sops=include_sops)
elif include_sops and \
output_node.childTypeCategory() == hou.sopNodeTypeCategory():
out = get_obj_node_output(output_node)
if out:
out_list += [out]
return out_list
def get_resolution_from_doc(doc):
"""Get resolution from the given asset document. """

View file

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
"""Creator for Unreal Static Meshes."""
from openpype.hosts.houdini.api import plugin
from openpype.lib import BoolDef, EnumDef
import hou
class CreateStaticMesh(plugin.HoudiniCreator):
"""Static Meshes as FBX. """
identifier = "io.openpype.creators.houdini.staticmesh.fbx"
label = "Static Mesh (FBX)"
family = "staticMesh"
icon = "fa5s.cubes"
default_variants = ["Main"]
def create(self, subset_name, instance_data, pre_create_data):
instance_data.update({"node_type": "filmboxfbx"})
instance = super(CreateStaticMesh, self).create(
subset_name,
instance_data,
pre_create_data)
# get the created rop node
instance_node = hou.node(instance.get("instance_node"))
# prepare parms
output_path = hou.text.expandString(
"$HIP/pyblish/{}.fbx".format(subset_name)
)
parms = {
"startnode": self.get_selection(),
"sopoutput": output_path,
# vertex cache format
"vcformat": pre_create_data.get("vcformat"),
"convertunits": pre_create_data.get("convertunits"),
# set render range to use frame range start-end frame
"trange": 1,
"createsubnetroot": pre_create_data.get("createsubnetroot")
}
# set parms
instance_node.setParms(parms)
# Lock any parameters in this list
to_lock = ["family", "id"]
self.lock_parameters(instance_node, to_lock)
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]
def get_pre_create_attr_defs(self):
"""Add settings for users. """
attrs = super(CreateStaticMesh, self).get_pre_create_attr_defs()
createsubnetroot = BoolDef("createsubnetroot",
tooltip="Create an extra root for the "
"Export node when it's a "
"subnetwork. This causes the "
"exporting subnetwork node to be "
"represented in the FBX file.",
default=False,
label="Create Root for Subnet")
vcformat = EnumDef("vcformat",
items={
0: "Maya Compatible (MC)",
1: "3DS MAX Compatible (PC2)"
},
default=0,
label="Vertex Cache Format")
convert_units = BoolDef("convertunits",
tooltip="When on, the FBX is converted"
"from the current Houdini "
"system units to the native "
"FBX unit of centimeters.",
default=False,
label="Convert Units")
return attrs + [createsubnetroot, vcformat, convert_units]
def get_dynamic_data(
self, variant, task_name, asset_doc, project_name, host_name, instance
):
"""
The default subset name templates for Unreal include {asset} and thus
we should pass that along as dynamic data.
"""
dynamic_data = super(CreateStaticMesh, self).get_dynamic_data(
variant, task_name, asset_doc, project_name, host_name, instance
)
dynamic_data["asset"] = asset_doc["name"]
return dynamic_data
def get_selection(self):
"""Selection Logic.
how self.selected_nodes should be processed to get
the desirable node from selection.
Returns:
str : node path
"""
selection = ""
if self.selected_nodes:
selected_node = self.selected_nodes[0]
# Accept sop level nodes (e.g. /obj/geo1/box1)
if isinstance(selected_node, hou.SopNode):
selection = selected_node.path()
self.log.debug(
"Valid SopNode selection, 'Export' in filmboxfbx"
" will be set to '%s'.", selected_node
)
# Accept object level nodes (e.g. /obj/geo1)
elif isinstance(selected_node, hou.ObjNode):
selection = selected_node.path()
self.log.debug(
"Valid ObjNode selection, 'Export' in filmboxfbx "
"will be set to the child path '%s'.", selection
)
else:
self.log.debug(
"Selection isn't valid. 'Export' in "
"filmboxfbx will be empty."
)
else:
self.log.debug(
"No Selection. 'Export' in filmboxfbx will be empty."
)
return selection

View file

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
"""Fbx Loader for houdini. """
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
class FbxLoader(load.LoaderPlugin):
"""Load fbx files. """
label = "Load FBX"
icon = "code-fork"
color = "orange"
order = -10
families = ["staticMesh", "fbx"]
representations = ["fbx"]
def load(self, context, name=None, namespace=None, data=None):
# get file path from context
file_path = self.filepath_from_context(context)
file_path = file_path.replace("\\", "/")
# get necessary data
namespace, node_name = self.get_node_name(context, name, namespace)
# create load tree
nodes = self.create_load_node_tree(file_path, node_name, name)
self[:] = nodes
# Call containerise function which does some automations for you
# like moving created nodes to the AVALON_CONTAINERS subnetwork
containerised_nodes = pipeline.containerise(
node_name,
namespace,
nodes,
context,
self.__class__.__name__,
suffix="",
)
return containerised_nodes
def update(self, container, representation):
node = container["node"]
try:
file_node = next(
n for n in node.children() if n.type().name() == "file"
)
except StopIteration:
self.log.error("Could not find node of type `file`")
return
# Update the file path from representation
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
file_node.setParms({"file": file_path})
# Update attribute
node.setParms({"representation": str(representation["_id"])})
def remove(self, container):
node = container["node"]
node.destroy()
def switch(self, container, representation):
self.update(container, representation)
def get_node_name(self, context, name=None, namespace=None):
"""Define node name."""
if not namespace:
namespace = context["asset"]["name"]
if namespace:
node_name = "{}_{}".format(namespace, name)
else:
node_name = name
return namespace, node_name
def create_load_node_tree(self, file_path, node_name, subset_name):
"""Create Load network.
you can start building your tree at any obj level.
it'll be much easier to build it in the root obj level.
Afterwards, your tree will be automatically moved to
'/obj/AVALON_CONTAINERS' subnetwork.
"""
import hou
# Get the root obj level
obj = hou.node("/obj")
# Create a new obj geo node
parent_node = obj.createNode("geo", node_name=node_name)
# In older houdini,
# when reating a new obj geo node, a default file node will be
# automatically created.
# so, we will delete it if exists.
file_node = parent_node.node("file1")
if file_node:
file_node.destroy()
# Create a new file node
file_node = parent_node.createNode("file", node_name=node_name)
file_node.setParms({"file": file_path})
# Create attribute delete
attribdelete_name = "attribdelete_{}".format(subset_name)
attribdelete = parent_node.createNode("attribdelete",
node_name=attribdelete_name)
attribdelete.setParms({"ptdel": "fbx_*"})
attribdelete.setInput(0, file_node)
# Create a Null node
null_name = "OUT_{}".format(subset_name)
null = parent_node.createNode("null", node_name=null_name)
null.setInput(0, attribdelete)
# Ensure display flag is on the file_node input node and not on the OUT
# node to optimize "debug" displaying in the viewport.
file_node.setDisplayFlag(True)
# Set new position for children nodes
parent_node.layoutChildren()
# Return all the nodes
return [parent_node, file_node, attribdelete, null]

View file

@ -14,7 +14,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"imagesequence",
"usd",
"usdrender",
"redshiftproxy"
"redshiftproxy",
"staticMesh"
]
hosts = ["houdini"]
@ -59,6 +60,10 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
elif node_type == "Redshift_Proxy_Output":
out_node = node.parm("RS_archive_sopPath").evalAsNode()
elif node_type == "filmboxfbx":
out_node = node.parm("startnode").evalAsNode()
else:
raise KnownPublishError(
"ROP node type '{}' is not supported.".format(node_type)

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""Collector for staticMesh types. """
import pyblish.api
class CollectStaticMeshType(pyblish.api.InstancePlugin):
"""Collect data type for fbx instance."""
hosts = ["houdini"]
families = ["staticMesh"]
label = "Collect type of staticMesh"
order = pyblish.api.CollectorOrder
def process(self, instance):
if instance.data["creator_identifier"] == "io.openpype.creators.houdini.staticmesh.fbx": # noqa: E501
# Marking this instance as FBX triggers the FBX extractor.
instance.data["families"] += ["fbx"]

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""Fbx Extractor for houdini. """
import os
import pyblish.api
from openpype.pipeline import publish
from openpype.hosts.houdini.api.lib import render_rop
import hou
class ExtractFBX(publish.Extractor):
label = "Extract FBX"
families = ["fbx"]
hosts = ["houdini"]
order = pyblish.api.ExtractorOrder + 0.1
def process(self, instance):
# get rop node
ropnode = hou.node(instance.data.get("instance_node"))
output_file = ropnode.evalParm("sopoutput")
# get staging_dir and file_name
staging_dir = os.path.normpath(os.path.dirname(output_file))
file_name = os.path.basename(output_file)
# render rop
self.log.debug("Writing FBX '%s' to '%s'", file_name, staging_dir)
render_rop(ropnode)
# prepare representation
representation = {
"name": "fbx",
"ext": "fbx",
"files": file_name,
"stagingDir": staging_dir
}
# A single frame may also be rendered without start/end frame.
if "frameStart" in instance.data and "frameEnd" in instance.data:
representation["frameStart"] = instance.data["frameStart"]
representation["frameEnd"] = instance.data["frameEnd"]
# set value type for 'representations' key to list
if "representations" not in instance.data:
instance.data["representations"] = []
# update instance data
instance.data["stagingDir"] = staging_dir
instance.data["representations"].append(representation)

View file

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
import pyblish.api
from openpype.pipeline import PublishValidationError
from openpype.hosts.houdini.api.action import (
SelectInvalidAction,
SelectROPAction,
)
from openpype.hosts.houdini.api.lib import get_obj_node_output
import hou
class ValidateFBXOutputNode(pyblish.api.InstancePlugin):
"""Validate the instance Output Node.
This will ensure:
- The Output Node Path is set.
- The Output Node Path refers to an existing object.
- The Output Node is a Sop or Obj node.
- The Output Node has geometry data.
- The Output Node doesn't include invalid primitive types.
"""
order = pyblish.api.ValidatorOrder
families = ["fbx"]
hosts = ["houdini"]
label = "Validate FBX Output Node"
actions = [SelectROPAction, SelectInvalidAction]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes),
title="Invalid output node(s)"
)
@classmethod
def get_invalid(cls, instance):
output_node = instance.data.get("output_node")
# Check if The Output Node Path is set and
# refers to an existing object.
if output_node is None:
rop_node = hou.node(instance.data["instance_node"])
cls.log.error(
"Output node in '%s' does not exist. "
"Ensure a valid output path is set.", rop_node.path()
)
return [rop_node]
# Check if the Output Node is a Sop or an Obj node
# also, list all sop output nodes inside as well as
# invalid empty nodes.
all_out_sops = []
invalid = []
# if output_node is an ObjSubnet or an ObjNetwork
if output_node.childTypeCategory() == hou.objNodeTypeCategory():
for node in output_node.allSubChildren():
if node.type().name() == "geo":
out = get_obj_node_output(node)
if out:
all_out_sops.append(out)
else:
invalid.append(node) # empty_objs
cls.log.error(
"Geo Obj Node '%s' is empty!",
node.path()
)
if not all_out_sops:
invalid.append(output_node) # empty_objs
cls.log.error(
"Output Node '%s' is empty!",
node.path()
)
# elif output_node is an ObjNode
elif output_node.type().name() == "geo":
out = get_obj_node_output(output_node)
if out:
all_out_sops.append(out)
else:
invalid.append(node) # empty_objs
cls.log.error(
"Output Node '%s' is empty!",
node.path()
)
# elif output_node is a SopNode
elif output_node.type().category().name() == "Sop":
all_out_sops.append(output_node)
# Then it's a wrong node type
else:
cls.log.error(
"Output node %s is not a SOP or OBJ Geo or OBJ SubNet node. "
"Instead found category type: %s %s",
output_node.path(), output_node.type().category().name(),
output_node.type().name()
)
return [output_node]
# Check if all output sop nodes have geometry
# and don't contain invalid prims
invalid_prim_types = ["VDB", "Volume"]
for sop_node in all_out_sops:
# Empty Geometry test
if not hasattr(sop_node, "geometry"):
invalid.append(sop_node) # empty_geometry
cls.log.error(
"Sop node '%s' doesn't include any prims.",
sop_node.path()
)
continue
frame = instance.data.get("frameStart", 0)
geo = sop_node.geometryAtFrame(frame)
if len(geo.iterPrims()) == 0:
invalid.append(sop_node) # empty_geometry
cls.log.error(
"Sop node '%s' doesn't include any prims.",
sop_node.path()
)
continue
# Invalid Prims test
for prim_type in invalid_prim_types:
if geo.countPrimType(prim_type) > 0:
invalid.append(sop_node) # invalid_prims
cls.log.error(
"Sop node '%s' includes invalid prims of type '%s'.",
sop_node.path(), prim_type
)
if invalid:
return invalid

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""Validator for correct naming of Static Meshes."""
import pyblish.api
from openpype.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from openpype.pipeline.publish import ValidateContentsOrder
from openpype.hosts.houdini.api.action import SelectInvalidAction
from openpype.hosts.houdini.api.lib import get_output_children
class ValidateMeshIsStatic(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate mesh is static.
It checks if output node is time dependent.
"""
families = ["staticMesh"]
hosts = ["houdini"]
label = "Validate Mesh is Static"
order = ValidateContentsOrder + 0.1
actions = [SelectInvalidAction]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes)
)
@classmethod
def get_invalid(cls, instance):
invalid = []
output_node = instance.data.get("output_node")
if output_node is None:
cls.log.debug(
"No Output Node, skipping check.."
)
return
all_outputs = get_output_children(output_node)
for output in all_outputs:
if output.isTimeDependent():
invalid.append(output)
cls.log.error(
"Output node '%s' is time dependent.",
output.path()
)
return invalid

View file

@ -24,7 +24,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder
families = ["pointcache", "vdbcache"]
hosts = ["houdini"]
label = "Validate Output Node"
label = "Validate Output Node (SOP)"
actions = [SelectROPAction, SelectInvalidAction]
def process(self, instance):

View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""Validator for correct naming of Static Meshes."""
import pyblish.api
from openpype.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from openpype.pipeline.publish import (
ValidateContentsOrder,
RepairAction,
)
from openpype.hosts.houdini.api.action import SelectInvalidAction
from openpype.pipeline.create import get_subset_name
import hou
class FixSubsetNameAction(RepairAction):
label = "Fix Subset Name"
class ValidateSubsetName(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate Subset name.
"""
families = ["staticMesh"]
hosts = ["houdini"]
label = "Validate Subset Name"
order = ValidateContentsOrder + 0.1
actions = [FixSubsetNameAction, SelectInvalidAction]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes)
)
@classmethod
def get_invalid(cls, instance):
invalid = []
rop_node = hou.node(instance.data["instance_node"])
# Check subset name
subset_name = get_subset_name(
family=instance.data["family"],
variant=instance.data["variant"],
task_name=instance.data["task"],
asset_doc=instance.data["assetEntity"],
dynamic_data={"asset": instance.data["asset"]}
)
if instance.data.get("subset") != subset_name:
invalid.append(rop_node)
cls.log.error(
"Invalid subset name on rop node '%s' should be '%s'.",
rop_node.path(), subset_name
)
return invalid
@classmethod
def repair(cls, instance):
rop_node = hou.node(instance.data["instance_node"])
# Check subset name
subset_name = get_subset_name(
family=instance.data["family"],
variant=instance.data["variant"],
task_name=instance.data["task"],
asset_doc=instance.data["assetEntity"],
dynamic_data={"asset": instance.data["asset"]}
)
instance.data["subset"] = subset_name
rop_node.parm("subset").set(subset_name)
cls.log.debug(
"Subset name on rop node '%s' has been set to '%s'.",
rop_node.path(), subset_name
)

View file

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""Validator for correct naming of Static Meshes."""
import pyblish.api
from openpype.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from openpype.pipeline.publish import ValidateContentsOrder
from openpype.hosts.houdini.api.action import SelectInvalidAction
from openpype.hosts.houdini.api.lib import get_output_children
import hou
class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate name of Unreal Static Mesh.
This validator checks if output node name has a collision prefix:
- UBX
- UCP
- USP
- UCX
This validator also checks if subset name is correct
- {static mesh prefix}_{Asset-Name}{Variant}.
"""
families = ["staticMesh"]
hosts = ["houdini"]
label = "Unreal Static Mesh Name (FBX)"
order = ValidateContentsOrder + 0.1
actions = [SelectInvalidAction]
optional = True
collision_prefixes = []
static_mesh_prefix = ""
@classmethod
def apply_settings(cls, project_settings, system_settings):
settings = (
project_settings["houdini"]["create"]["CreateStaticMesh"]
)
cls.collision_prefixes = settings["collision_prefixes"]
cls.static_mesh_prefix = settings["static_mesh_prefix"]
def process(self, instance):
if not self.is_active(instance.data):
return
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes)
)
@classmethod
def get_invalid(cls, instance):
invalid = []
rop_node = hou.node(instance.data["instance_node"])
output_node = instance.data.get("output_node")
if output_node is None:
cls.log.debug(
"No Output Node, skipping check.."
)
return
if rop_node.evalParm("buildfrompath"):
# This validator doesn't support naming check if
# building hierarchy from path' is used
cls.log.info(
"Using 'Build Hierarchy from Path Attribute', skipping check.."
)
return
# Check nodes names
all_outputs = get_output_children(output_node, include_sops=False)
for output in all_outputs:
for prefix in cls.collision_prefixes:
if output.name().startswith(prefix):
invalid.append(output)
cls.log.error(
"Invalid node name: Node '%s' "
"includes a collision prefix '%s'",
output.path(), prefix
)
break
return invalid

View file

@ -19,6 +19,19 @@
],
"ext": ".ass"
},
"CreateStaticMesh": {
"enabled": true,
"default_variants": [
"Main"
],
"static_mesh_prefix": "S",
"collision_prefixes": [
"UBX",
"UCP",
"USP",
"UCX"
]
},
"CreateAlembicCamera": {
"enabled": true,
"default_variants": [
@ -102,6 +115,21 @@
"enabled": true,
"optional": true,
"active": true
},
"ValidateSubsetName": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateMeshIsStatic": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateUnrealStaticMeshName": {
"enabled": false,
"optional": true,
"active": true
}
}
}

View file

@ -39,6 +39,37 @@
]
},
{
"type": "dict",
"collapsible": true,
"key": "CreateStaticMesh",
"label": "Create Static Mesh",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "default_variants",
"label": "Default Variants",
"object_type": "text"
},
{
"type": "text",
"key": "static_mesh_prefix",
"label": "Static Mesh Prefix"
},
{
"type": "list",
"key": "collision_prefixes",
"label": "Collision Mesh Prefixes",
"object_type": "text"
}
]
},
{
"type": "schema_template",
"name": "template_create_plugin",

View file

@ -47,6 +47,18 @@
{
"key": "ValidateContainers",
"label": "ValidateContainers"
},
{
"key": "ValidateSubsetName",
"label": "Validate Subset Name"
},
{
"key": "ValidateMeshIsStatic",
"label": "Validate Mesh is Static"
},
{
"key": "ValidateUnrealStaticMeshName",
"label": "Validate Unreal Static Mesh Name"
}
]
}

View file

@ -21,10 +21,28 @@ class CreateArnoldAssModel(BaseSettingsModel):
ext: str = Field(Title="Extension")
class CreateStaticMeshModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
default_variants: list[str] = Field(
default_factory=list,
title="Default Products"
)
static_mesh_prefixes: str = Field("S", title="Static Mesh Prefix")
collision_prefixes: list[str] = Field(
default_factory=list,
title="Collision Prefixes"
)
class CreatePluginsModel(BaseSettingsModel):
CreateArnoldAss: CreateArnoldAssModel = Field(
default_factory=CreateArnoldAssModel,
title="Create Alembic Camera")
# "-" is not compatible in the new model
CreateStaticMesh: CreateStaticMeshModel = Field(
default_factory=CreateStaticMeshModel,
title="Create Static Mesh"
)
CreateAlembicCamera: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Alembic Camera")
@ -63,6 +81,19 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = {
"default_variants": ["Main"],
"ext": ".ass"
},
"CreateStaticMesh": {
"enabled": True,
"default_variants": [
"Main"
],
"static_mesh_prefix": "S",
"collision_prefixes": [
"UBX",
"UCP",
"USP",
"UCX"
]
},
"CreateAlembicCamera": {
"enabled": True,
"default_variants": ["Main"]
@ -136,6 +167,15 @@ class PublishPluginsModel(BaseSettingsModel):
ValidateContainers: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Latest Containers.")
ValidateSubsetName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Subset Name.")
ValidateMeshIsStatic: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Mesh is Static.")
ValidateUnrealStaticMeshName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Unreal Static Mesh Name.")
DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
@ -160,5 +200,20 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
"enabled": True,
"optional": True,
"active": True
},
"ValidateSubsetName": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateMeshIsStatic": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateUnrealStaticMeshName": {
"enabled": False,
"optional": True,
"active": True
}
}

View file

@ -1 +1 @@
__version__ = "0.1.2"
__version__ = "0.1.3"