Asset validation

Updated October 2026

Describes how validators check assets in the DCC before export, how project admins configure the rules, and what each built-in rule checks.

Validators catch problems before the file reaches Perforce or Unreal, such as a name that breaks the convention, a mesh that isn’t watertight, or a vertex skinned to too many bones. The Unreal plugin also checks asset names on import.

Enable and test a rule (project admin)

  1. Sign in to the web portal as an Admin. Click Projects, select your project, and click Validators.
  2. In the tree, select Global, an asset type such as Static Mesh, or a taxonomy node. Each rule is tagged inherited, overridden here or added here, and an inherited rule also shows the node it comes from.
  3. To add a rule, choose it from + add rule…. The validator reference shows which DCCs and asset types it covers. Use the toggle to turn the rule on or off, set the severity to error (blocks publishing) or warning (advisory), and enter its parameters. Editing an inherited rule creates an override at this node. To undo a change, click Reset on an overridden rule or Remove on a rule added here.
  4. The editor saves each change as you make it, so there is no Save button. To check a change, select another node and then select this one again.
  5. In the DCC, collect one valid and one invalid test instance for that node. The DCC caches conventions for up to five minutes, so wait or relaunch it, then click Validate.
  6. Check that the invalid instance shows the message you expect and, for an error, can’t be published. Check that the valid instance still passes.

If the project’s conventions are a Lua script, the tree is read-only. Change the rules in the script instead. See Authoring in Lua.

Resolve a validation failure (artist)

  1. Check only the instances you want to publish, and click Validate.
  2. Expand the failing rule to see the affected instance and its message. If the result lists objects, click Select Invalid to select them in the scene.
  3. If the rule has a Repair button, save your work and click it. Otherwise, fix the source by hand.
  4. After you change the scene’s structure, click Reset to collect again, then validate again. Before you publish, make sure no blocking error or destination conflict remains.
  5. If a project rule seems wrong, send the project admin the rule name, the message, the asset type and tags, and an example.

In Blender’s Dousen panel, the buttons are Run Validation, Fix and Publish Checked Instances. See the worked example.

Validator fields

Every validator, built-in or custom, is a Python class with these fields and a validate() method that performs the check:

FieldMeaning
rule_id A stable, dotted identifier grouped by area, such as mesh.watertight, naming.convention or texture.max_resolution. A convention uses it to turn the rule on.
name A readable name shown in the portal’s rule picker and in the DCC’s results list.
category Groups rules in the picker, and decides whether the Nanite / NonNanite gate applies. Examples are Mesh, Rig and Nanite.
hosts The DCCs that load the validator, such as ["blender"]. ["any"] loads it in every host.
severity What a failure means by default. error blocks the export. warning is advisory, and the artist can continue. A convention can override this for a project.
enabled Whether the rule is on by default. Some stricter or studio-specific rules ship turned off.
asset_types The asset types the rule applies to: static_mesh, skeletal_mesh, animation_sequence, texture, hda or vat. An empty list means any type.
params Typed values that admins can edit. Each is {key, type, default, label}, where type is int, float, bool, string or list. Examples are max_tris = 50000 and target_fps = 30.

The check returns whether it passed, a message such as "Exceeds limit of 50,000 tris", the affected objects, and an optional fix function. See Writing a custom validator.

The defaults on this page are the baseline. A project’s conventions can turn on a rule that is off by default, change a warning to an error, or tighten a parameter.

Where rules come from and how they are scoped

Validators are stored in the project’s naming-conventions document. You can attach a rule at three levels, and each applies to everything below it. For the entry format, see Validator entries.

  1. Global applies to every asset in the project.
  2. Asset type applies to every asset of one type.
  3. Taxonomy node applies to one node in the taxonomy tree, at any depth, and everything beneath it.

A deeper level overrides matching parameters from a shallower one, so a node can set a stricter texture limit for hero props than the global rule does. DousenCore stores each node’s effective rules in the compiled conventions, and a DCC uses the rules of the deepest matching node.

Texture size limits are validator rules (texture.max_resolution and texture.resolution_power_of_2), so you set them on the Validators tab.

Validators tab with a node tree on the left and the Static Mesh node open: a naming rule inherited from Global, a rule added here, and a polygon count rule overridden with a Maximum triangles value.
Static Mesh has one inherited rule, one added rule and one overridden rule. Screenshots on this page use a sample project.

The validator catalog

The catalog is the list of rules that the portal’s rule picker offers. It has two layers:

  1. A compiled baseline built into DousenCore, with naming, mesh, UV, scene, material, rig, animation, texture, HDA and VAT rules. It’s the fallback when a tenant hasn’t reported anything.
  2. A layer reported by the DCC addons. On startup, each addon (Blender, Maya, 3ds Max, Houdini, Substance Painter and Substance Designer) sends DousenCore the list of validator classes it has registered, including custom plugins from validators.extra_paths. Core accepts the report only from an Admin session, so an addon started by any other user skips it.

DousenCore merges the two layers for each tenant. A rule that appears only in the report is added to the catalog, with typed parameter fields in the rule picker. For a rule in both layers, Core keeps the baseline’s name, category, default severity and default state, and uses the report only to fill in parameters and asset types that the baseline leaves empty. When a DCC can’t reach the compiled conventions, it falls back to the validator catalog if DousenCore still serves it, and otherwise to the default rules built into the addon.

EndpointPurpose
GET /api/validator-catalog Returns the merged catalog (baseline plus reported rules) with each rule’s parameters. The portal’s rule picker uses it. Any signed-in user can call it.
POST /api/validator-catalog A DCC addon reports its rule list for the tenant. The addon must be running in an Admin session. Other users get a 403.

Validator reference

The built-in rules, by category. Default shows the default severity and whether the rule is on, and Parameters shows the baseline defaults. The Nanite, NonNanite and host-specific rules take their defaults from the addons, and the rest from DousenCore’s baseline catalog.

Naming

Every DCC reports these rules, which check names against the project’s compiled conventions. In Substance Painter, Substance Designer and Houdini, they check texture set, graph and HDA names. In those three, naming.convention checks only the prefix (Designer uses the material prefix, default M_, on graph names), and naming.no_special_chars doesn’t allow -. Houdini also allows . and :.

Rule Default Applies to Parameters Checks
naming.convention error · on any — Names start with the asset type’s prefix from the project convention (for example SM_ for a static mesh), and the export name meets the convention’s pattern and length rules. Fails if the scene is unsaved, or if the file name still contains an unresolved {placeholder}.
naming.no_special_chars error · on any — The file name and object names contain only letters, digits, _ and -. Maya also allows | and :.

Mesh: general hygiene

These geometry checks apply to every pipeline. The triangle limit is a general upper bound. The Nanite + Lumen — Static preset raises it to 2,000,000, and Nanite + Lumen — Foliage turns it off. Both rely on nonnanite.max_triangles for meshes without Nanite.

Rule Default Applies to Parameters Checks
mesh.poly_count error · on static_mesh, skeletal_mesh max_tris = 50000 Each mesh has at most max_tris triangles.
mesh.no_ngons warning · off static_mesh, skeletal_mesh — No faces with five or more sides.
mesh.no_zero_area error · on static_mesh, skeletal_mesh — No faces with zero area.
mesh.no_nonmanifold error · on static_mesh, skeletal_mesh — No non-manifold geometry, such as an edge shared by more than two faces. In Blender, open boundary edges also count, so an open mesh fails this rule.

Nanite: dynamic pipeline

For meshes that use Nanite and Lumen. Nanite needs no authored LODs, but open edges, flipped normals, floating geometry and sliver triangles cause visible problems. These rules run only when the asset’s materials allow Nanite (see the Nanite gate). uvs.min_coverage belongs to this category even though its ID starts with uvs..

Rule Default Applies to Parameters Checks
mesh.watertight error · on static_mesh — Every edge borders exactly two faces, so there are no open boundary edges. Nanite renders holes as missing triangles, and Lumen leaks light through gaps.
mesh.consistent_normals error · on static_mesh, skeletal_mesh — No flipped or inconsistently wound faces, detected from edge winding. Flipped faces show up as pink in the Nanite viewport.
mesh.no_sliver_triangles warning · on static_mesh, skeletal_mesh min_angle_deg = 5.0 No triangle has a corner angle smaller than min_angle_deg.
mesh.no_loose_geometry error · on static_mesh, skeletal_mesh — No loose vertices or wire edges that aren’t part of a face. Nanite can’t cluster them.
nanite.min_triangles warning · on static_mesh min_tris = 1000 Each mesh has at least min_tris triangles, so Nanite’s fixed per-mesh cost is worth paying. Below that, a traditional mesh is cheaper.
nanite.tri_vert_ratio warning · on static_mesh max_vert_ratio = 0.6 The ratio of vertices to triangles stays at or below max_vert_ratio. Extra vertices point to UV or normal seams that split geometry and hurt Nanite clustering.
uvs.min_coverage warning · on static_mesh min_coverage = 0.65 UV0 islands fill at least min_coverage of the 0–1 space, which keeps texel density efficient. A mesh with no UVs fails.

NonNanite: traditional pipeline

These rules run instead of the Nanite rules when the asset’s materials don’t allow Nanite. They cover a traditional mesh with authored LODs and a baked lightmap.

Rule Default Applies to Parameters Checks
nonnanite.max_triangles warning · on static_mesh max_tris = 60000 A triangle budget for meshes without Nanite, which cost more per triangle than Nanite meshes. Its default (60,000) is tighter than mesh.poly_count only when a preset or node raises mesh.poly_count, as Nanite + Lumen — Static does.
nonnanite.lod_chain warning · on static_mesh min_lods = 1, tris_threshold = 20000 A mesh with more than tris_threshold triangles has at least min_lods authored LODs, detected by name: <base>_LOD1, _LOD2 and so on. Meshes below the threshold need no LODs.
nonnanite.lightmap_uvs warning · on static_mesh — The mesh has a second UV channel (UV1) for the baked lightmap.
nonnanite.max_bounds_ratio warning · on static_mesh max_extent = 50 (m), dense_tris = 100000 Flags a mesh only when it is both large (its bounding-box diagonal is over max_extent, converted to meters whatever the scene units) and dense (over dense_tris triangles). Only that combination culls poorly without Nanite.

UVs

Rule Default Applies to Parameters Checks
uvs.in_range warning · off static_mesh, skeletal_mesh — UV coordinates stay within the 0–1 tile.
uvs.no_overlap_lightmap warning · on static_mesh — No overlapping UVs in the lightmap channel. Overlaps bake as light bleed.

Scene

Rule Default Applies to Parameters Checks
scene.transforms_applied error · on static_mesh, skeletal_mesh — Scale is 1 and rotation is 0 on every mesh object. Blender and Maya offer an automatic fix that applies rotation and scale.
scene.no_hidden_objects warning · on static_mesh, skeletal_mesh, animation_sequence — No mesh objects are hidden. Blender checks viewport and render visibility. Maya checks the visibility attribute.
scene.single_root_skeletal error · off skeletal_mesh — A skeleton has a single root bone (a single root joint in Maya).
scene.no_loose_vertices error · on static_mesh, skeletal_mesh — No vertices that aren’t connected to an edge.

Materials

Rule Default Applies to Parameters Checks
materials.no_missing_slots error · on static_mesh, skeletal_mesh — No empty or unlinked material slots on a mesh.
materials.expected_count warning · off static_mesh, skeletal_mesh max_materials = 3 No mesh has more than max_materials materials. See the note below this table.
materials.no_empty_texture_sets warning · on texture — Every texture set has at least one layer (Substance Painter).

Max materials (max_materials) is an upper bound, and it defaults to 3 in Blender and Maya if unset. A project that still sets the older expected_count parameter keeps the exact-match behavior: each mesh must have exactly that many materials.

Rig

Rule Default Applies to Parameters Checks
rig.bone_naming warning · on skeletal_mesh pattern = ^[a-z][a-z0-9_]*(\.[LR]|_[lr])?$ Bone names match the regular expression: lowercase, with an optional left or right suffix.
rig.hierarchy error · on skeletal_mesh check_symmetry = false The skeleton has a single root bone. When check_symmetry is on, every left bone needs a matching right bone.
rig.bone_count warning · on skeletal_mesh max_bones = 256 The skeleton has at most max_bones bones.
rig.max_influences error · on skeletal_mesh max_influences = 4 No vertex is skinned to more than max_influences bones.
rig.unweighted_vertices error · on skeletal_mesh — Every vertex has at least one skin weight.
rig.root_at_origin error · on skeletal_mesh — The root bone is at the world origin.
rig.bind_pose warning · off skeletal_mesh — The skeleton is in its bind pose, with no leftover rotations. In Blender, only armatures set to Rest Position are checked.
rig.no_negative_scale error · on skeletal_mesh — No bone has negative scale, which breaks in the engine.

Animation

Rule Default Applies to Parameters Checks
anim.frame_range error · on animation_sequence — The clip has keyframes and a valid frame range.
anim.fps_match error · on animation_sequence target_fps = 30 The scene frame rate equals target_fps.
anim.root_motion warning · off animation_sequence expect_stationary = false, root_bone = root The root bone moves only when it should. Flags a moving root when expect_stationary is on, and a stationary root when it is off.
anim.no_keys_on_frame_zero warning · on animation_sequence — No keyframes on frame 0. Unreal expects frame 1 or later.
anim.no_scale_keys warning · on animation_sequence allow_root_scale = false, root_bone = root No scale keyframes on bones. When allow_root_scale is on, the root bone may have them.
anim.bone_count_match error · off animation_sequence expected_bone_count = 0 The skeleton has exactly expected_bone_count bones. A value of 0 means any count, so the rule passes. Set the real count to check it.

Textures

Rule Default Applies to Parameters Checks
texture.resolution_power_of_2 error · on texture — The width and height of each texture set are powers of two.
texture.max_resolution warning · on texture max_resolution = 4096 The width and height of each texture set are at most max_resolution.
texture.channel_completeness error · on texture required_channels = [BaseColor, Normal, Roughness] Each texture set has every channel in required_channels.
texture.channel_gamma error · on texture — Color channels use sRGB, and data channels such as Normal and Roughness use Linear. The check also fails if the Painter version can’t report channel color spaces.

Houdini: HDA and VAT

Rule Default Applies to Parameters Checks
hda.has_definition error · on hda — Each selected node is an HDA instance with a definition. Fails if no node is selected.
hda.version_set warning · on hda — The HDA has a version string.
hda.has_description warning · on hda — The HDA has a label that differs from its type name.
vat.resolution_power_of_2 error · on vat — The VAT texture resolution parameters (posmap_res, normalmap_res and vm_picture_res) are powers of two.
vat.output_files_exist error · on vat expected_outputs = [posmap, normalmap, geo_output] Each parameter in expected_outputs exists on the node and has a path set. The check doesn’t test whether the file is on disk.

Host-specific rules

Some addons have checks of their own that aren’t in the baseline. They report themselves to the catalog on startup like a custom plugin, together with the DCCs that implement them. The rule picker lists these rules for every node and adds the hosts to the name, for example (blender only). The rule’s detail shows hosts: followed by the DCCs. Only a host that implements the rule runs it, and a DCC that doesn’t implement it skips the rule without a warning. If no Desktop has reported hosts yet, the catalog has none to show, and a DCC without a validator for the rule shows the warning described in DCC coverage.

Only Designer’s two resolution rules (graph.resolution_power_of_2 and its texture.resolution_power_of_2 alias) declare parameters, so the Textures rule can show Max resolution (px) and Require square output fields that only Designer uses. The other host-specific rules have no input fields in the portal.

3ds Max

Rule Default Parameters Checks
scene.units error · on unit_type = 5 The scene units match unit_type. The default, 5, is centimeters.
scene.frame_rate error · on fps = 24 The scene frame rate equals fps.
scene.no_unused_layers error · on — No empty layers other than layer 0.
rig.frozen_transforms error · on tolerance = 0.0001 Every mesh has its position at zero and its scale at 1, within tolerance. If it fails, reset the transform before you export.
rig.no_bone_scale error · on tolerance = 0.0001 No bone or Biped object has non-uniform scale.
animation.range error · on start_frame, end_frame The animation range starts and ends on the frames you set. The check passes if you set neither.
animation.no_redundant_keys error · on tolerance = 0.00001 No key repeats the value of the keys on either side of it.
uvs.has_uvs error · on — Every mesh has UV channel 1.
uvs.no_overlapping error · on channel = 2 No two faces on channel, the lightmap channel by default, share the same UV vertices. This is a duplicate-face check, not a full overlap test.
materials.naming error · on prefix = M_ Every material name starts with prefix.
materials.no_missing error · on — No mesh is missing a material or uses a default material.

Substance Designer

Rule Default Parameters Checks
graph.output_completeness error · on required_outputs = [BaseColor, Normal, Roughness] Every graph has an output node for each name in required_outputs.
graph.resolution_power_of_2 error · on max_resolution = 4096, require_square = true Each graph’s output size is square (when require_square is on) and no larger than max_resolution. It doesn’t test for a power of two, because Designer stores sizes as power-of-two exponents. Designer reports the same check as texture.resolution_power_of_2 too.
graph.no_unconnected_nodes warning · on — No node is disconnected from the graph, with no input or output connections.

DCC coverage

Blender, Maya and 3ds Max cover mesh authoring, and the other DCCs cover their own asset types. Every DCC reports the Naming rules.

Category Blender Maya 3ds Max Painter Designer Houdini
Naming✓✓✓✓✓✓
Mesh hygiene✓✓✓———
Nanite✓✓✓———
NonNanite✓✓✓———
UVs✓✓✓*———
Scene✓✓✓*———
Materials✓✓✓*✓——
Rig✓✓✓*———
Animation✓✓✓*———
Textures———✓——
Graph————✓—
HDA / VAT—————✓

3ds Max uses the same rule IDs as Blender and Maya for Naming, Mesh hygiene, Nanite and NonNanite, and also for scene.transforms_applied, uvs.min_coverage, uvs.no_overlap_lightmap and materials.no_missing_slots. In the categories marked ✓*, it covers the rest with its own host-specific rules. For example, it checks the frame rate with scene.frame_rate instead of anim.fps_match, and it has no rig.bone_count rule.

If a project turns on a rule that no DCC has a validator for, or before Desktop has reported which DCCs implement it, a DCC without a validator shows this warning: "No validator plugin found for rule 'rule id' on host 'host' — rule was skipped, not run."

The Nanite / NonNanite gate

Only one of the Nanite and NonNanite categories runs for each asset. Dousen picks it from the mesh’s material assignments in the project’s material catalog:

When Unreal imports a static mesh with a material assignment, it sets the mesh’s NaniteSettings.bEnabled from the same material lookup, so in the common case the export checks match the mesh’s Nanite setting. An unresolved assignment, or a geometry profile with nanite: false, can make them differ. For the import rules, see How the import sets Nanite.

The Nanite / NonNanite gate is a preview feature. Which rules belong to each category, and how the gate handles edge cases such as unassigned or mixed materials, may change.

Writing a custom validator

To add your own checks, write a Python validator plugin and add its folder to the validators.extra_paths pipeline setting. Each entry can be:

For local testing, you can also set DOUSEN_VALIDATOR_PATHS to absolute folders, separated by your platform’s path separator.

A validator subclasses ValidatorPlugin, sets the same fields as the built-in rules, and implements validate(), so it gets the same kind of catalog entry, with typed parameter inputs in the rule picker. Register it with the @register_plugin decorator. Import everything from dousen.validators:

ImportRole
ValidatorPlugin The base class you subclass.
register_plugin A decorator that adds your class to the registry, so Dousen discovers it and reports it to the catalog.
CheckResult The return type, an alias for (bool, str, list[str], Callable | None). You can use it as the return annotation.

validate(self, context, params) must return a 4-tuple:

  1. passed (bool): True if the check passed. validate() only reports pass or fail. The rule’s severity decides whether a failure blocks export (error) or is advisory (warning).
  2. message (str): the explanation shown next to a failed result. Use "" when the check passes.
  3. affected_objects (list[str]): the names of the objects or faces that failed, so the panel can point at them. Use [] when the check passes.
  4. fix_fn (Callable | None): an optional function, with no arguments, that fixes the problem. Return it to make the panel show a Repair button (Fix in Blender) that runs it and then validates again. Return None when there’s no safe automatic fix.

Import DCC modules inside validate() and the fix function. Dousen imports your class in every host, but a host’s API (bpy, bmesh, pymxs, Maya cmds) exists only in the hosts you list in hosts. The first argument is the host’s context: bpy.context in Blender, and a dictionary in the other hosts. params holds the admin-configured parameters for this asset, so read your keys from it with defaults.

PYTHON — a Blender custom validator, with an auto-fixfrom dousen.validators import ValidatorPlugin, CheckResult, register_plugin


@register_plugin
class NoNgonsValidator(ValidatorPlugin):
    rule_id     = "mesh.no_ngons_custom"   # unique, dotted, namespaced
    name        = "No ngon faces"          # shown in the picker + results list
    category    = "Mesh"                   # groups it in the picker
    hosts       = ["blender"]              # which DCCs load it; ["any"] = all
    severity    = "warning"                # default fail level; a convention may override
    enabled     = True                     # on out of the box
    asset_types = ["static_mesh"]          # [] = any type
    params      = [
        {"key": "max_sides", "type": "int", "default": 4, "label": "Max sides"},
    ]

    def validate(self, bpy_context, params) -> CheckResult:
        import bmesh  # DCC import stays inside validate()

        limit = int(params.get("max_sides", 4))
        offenders = []
        for obj in bpy_context.scene.objects:
            if obj.type != "MESH":
                continue
            bm = bmesh.new()
            bm.from_mesh(obj.data)
            ngons = sum(1 for f in bm.faces if len(f.verts) > limit)
            bm.free()
            if ngons:
                offenders.append(f"{obj.name} ({ngons} ngons)")

        if offenders:
            # An auto-fix: triangulate the offending meshes on demand.
            def _fix():
                import bmesh
                for obj in bpy_context.scene.objects:
                    if obj.type != "MESH":
                        continue
                    bm = bmesh.new()
                    bm.from_mesh(obj.data)
                    bmesh.ops.triangulate(bm, faces=bm.faces)
                    bm.to_mesh(obj.data)
                    bm.free()

            return False, f"{len(offenders)} mesh(es) contain ngons", offenders, _fix

        return True, "", [], None   # passed: empty message, no offenders, no fix

A new rule appears in the portal’s rule picker after an Admin’s DCC has reported it. To give every artist the same checks with no per-machine setup, keep custom validators in Perforce. See Loading studio tools.

Sharing one check across several DCCs

Many rules use the same logic in every DCC and differ only in how they read the scene. The built-in validators split such rules into two classes, and yours can too:

This is why mesh.poly_count appears for Blender, Maya and 3ds Max in the coverage table.

PYTHON — the base owns validate(); it calls an abstract hookclass BasePolyCountValidator(ValidatorPlugin):
    rule_id     = "mesh.poly_count"        # metadata lives on the base
    name        = "Polygon count within project limit"
    category    = "Mesh"
    severity    = "error"
    asset_types = ["static_mesh", "skeletal_mesh"]

    def get_mesh_tri_counts(self, context, params):
        """DCC-specific hook — subclass returns [(name, tri_count), ...]."""
        raise NotImplementedError

    def validate(self, context, params) -> CheckResult:
        max_tris = int(params.get("max_tris", 50000))
        offenders = []
        for name, tris in self.get_mesh_tri_counts(context, params):   # ← calls the hook
            if tris > max_tris:
                offenders.append(f"{name} ({tris:,} tris)")
        if offenders:
            return False, f"Exceeds limit of {max_tris:,} tris", offenders, None
        return True, "", [], None          # ← the 4-tuple is assembled here, once
PYTHON — the Maya subclass overrides only the data hook@register_plugin
class MayaPolyCountValidator(BasePolyCountValidator):
    hosts = ["maya"]                       # the only metadata it adds

    def get_mesh_tri_counts(self, context, params):
        import maya.cmds as cmds           # host import stays inside the hook
        return [
            (t, cmds.polyEvaluate(t, triangle=True))
            for t in _mesh_transforms()
        ]                                  # returns raw data — NOT the 4-tuple

The Maya subclass doesn’t override validate(), so the runner calls BasePolyCountValidator.validate(). That method gets the raw counts from Maya’s get_mesh_tri_counts(), applies the max_tris comparison, and returns the tuple.

Each base class defines the data method that its check needs:

Base classAbstract data methodSubclass returns
BasePolyCountValidatorget_mesh_tri_counts[(name, tris)]
BaseWatertightValidator get_mesh_boundary_edge_counts [(name, open_edge_count)]
BaseConsistentNormalsValidator get_mesh_face_loops [(name, [face_vert_indices])]
BaseNoSliverTrianglesValidator get_mesh_triangles [(name, [(p0, p1, p2)])]
BaseNonNaniteMaxBoundsRatioValidator get_mesh_bounds_and_tris (and scene_unit_scale) [(name, bbox_min, bbox_max, tris)]

For a check that runs in one host, subclass ValidatorPlugin directly and return the 4-tuple. If the same decision must run in two or more DCCs, use a base class with an abstract data method and a subclass for each host, so the hosts can’t drift apart.

Validation history and project health

The DCC addons record each validation run in DousenCore, under the email address of the signed-in user. Each instance you validate in the Publish tab logs its own run. DousenCore also stores runs that a studio tool posts to POST /api/projects/:id/validators/log. The portal shows the runs in three places:

Validation history tab with total, passed, warning and error counts, All, Pass, Warn and Error filter buttons, and a table of runs by user, status and time.
Validation history lists one row per run. The buttons filter by status. This screenshot shows sample runs.
EndpointPurpose
POST /api/projects/:id/validators/log Records the result of a validation run. The DCC addons call it, and DousenCore takes the user from the authentication token.
GET /api/projects/:id/health-summary Returns pass, warning and error trends, changelist activity and top offenders for a period. The free plan gets a 403.

Admins and artists

Worked example: an asset fails a validator

This example uses Blender’s Dousen panel. Maya, 3ds Max, Substance Painter and Substance Designer work the same way with the Validate and Publish buttons on the Qt Publish tab. Houdini uses its Dousen: Validate shelf tool.

  1. Run the validators.

    In the Dousen panel, check the instances you want to publish, open the Validation panel, and click Run Validation. The panel lists the rules by category, and you can expand a failing rule to see its message and affected objects. Say naming.convention fails as an error because a name doesn’t start with the project’s prefix. The status bar shows "Dousen validation: 1 error(s), 2 warning(s)".

  2. Fix the error.

    If the rule has a Fix button, click it. Dousen applies the fix and runs the validation again. Otherwise, rename the object yourself and click Run Validation again.

  3. Try to publish with an error.

    The Publish Checked Instances button in the Validation panel is disabled. If you click the button in the main Dousen panel instead, Blender shows "Fix validation errors before publishing", and nothing is exported or published.

  4. Publish with only warnings.

    Warnings don’t block publishing, but you have to confirm them. When only warnings remain (say, a nonnanite.max_triangles warning), Publish Checked Instances opens a dialog with the option Publish despite validation warnings. Turn it on to publish anyway, or cancel and fix the warnings first. If you publish without the option, Blender shows "Review validation warnings before publishing". On the Qt Publish tab, a dialog asks "N warning(s) found. Publish anyway?" instead.