Implement comprehensive material definition validation logic
This commit is contained in:
@@ -8033,6 +8033,7 @@ function validateProjectResources(
|
||||
}
|
||||
|
||||
registerAuthoredId(material.id, path, seenIds, diagnostics);
|
||||
validateMaterialDef(material, path, document.assets, diagnostics);
|
||||
}
|
||||
|
||||
for (const [assetKey, asset] of Object.entries(document.assets)) {
|
||||
@@ -8100,6 +8101,216 @@ function validateProjectResources(
|
||||
}
|
||||
}
|
||||
|
||||
function isMaterialMapImageAsset(asset: ProjectAssetRecord): boolean {
|
||||
if (asset.kind !== "image") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceName = asset.sourceName.toLowerCase();
|
||||
|
||||
return (
|
||||
asset.mimeType !== "image/x-exr" &&
|
||||
asset.mimeType !== "image/vnd.radiance" &&
|
||||
!sourceName.endsWith(".exr") &&
|
||||
!sourceName.endsWith(".hdr")
|
||||
);
|
||||
}
|
||||
|
||||
function validateMaterialTags(
|
||||
tags: unknown,
|
||||
path: string,
|
||||
diagnostics: SceneDiagnostic[]
|
||||
) {
|
||||
if (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string")) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-tags",
|
||||
"Material tags must remain a string array.",
|
||||
`${path}.tags`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateStarterMaterialDef(
|
||||
material: StarterMaterialDef,
|
||||
path: string,
|
||||
diagnostics: SceneDiagnostic[]
|
||||
) {
|
||||
if (
|
||||
material.workflow !== "roughness-only" &&
|
||||
material.workflow !== "metallic-roughness" &&
|
||||
material.workflow !== "specular-roughness"
|
||||
) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-workflow",
|
||||
"Starter material workflow must be roughness-only, metallic-roughness, or specular-roughness.",
|
||||
`${path}.workflow`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
material.previewImageName !== "preview.webp" &&
|
||||
material.previewImageName !== "preview_sphere.webp"
|
||||
) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-preview-image",
|
||||
"Starter material preview image must be preview.webp or preview_sphere.webp.",
|
||||
`${path}.previewImageName`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isPositiveFiniteNumber(material.sizeCm.width) ||
|
||||
!isPositiveFiniteNumber(material.sizeCm.height)
|
||||
) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-size",
|
||||
"Starter material tile size must be positive.",
|
||||
`${path}.sizeCm`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateCustomMaterialTextureRef(
|
||||
material: CustomMaterialDef,
|
||||
slot: MaterialTextureSlot,
|
||||
assets: Record<string, ProjectAssetRecord>,
|
||||
path: string,
|
||||
diagnostics: SceneDiagnostic[]
|
||||
) {
|
||||
const textureRef = material.textures[slot];
|
||||
|
||||
if (textureRef === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = assets[textureRef.assetId];
|
||||
|
||||
if (asset === undefined) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"missing-material-texture-asset",
|
||||
`Material ${slot} texture asset ${textureRef.assetId} does not exist.`,
|
||||
`${path}.textures.${slot}.assetId`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (asset.kind !== "image") {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-texture-asset-kind",
|
||||
`Material ${slot} texture asset ${textureRef.assetId} must be an image asset.`,
|
||||
`${path}.textures.${slot}.assetId`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMaterialMapImageAsset(asset)) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-texture-image-format",
|
||||
`Material ${slot} texture asset ${textureRef.assetId} must be a browser image, not HDR or EXR.`,
|
||||
`${path}.textures.${slot}.assetId`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateCustomMaterialDef(
|
||||
material: CustomMaterialDef,
|
||||
path: string,
|
||||
assets: Record<string, ProjectAssetRecord>,
|
||||
diagnostics: SceneDiagnostic[]
|
||||
) {
|
||||
if (!isHexColorString(material.albedoColorHex)) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-albedo-color",
|
||||
"Custom material albedo color must use #RRGGBB format.",
|
||||
`${path}.albedoColorHex`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (const [property, value] of [
|
||||
["opacity", material.opacity],
|
||||
["roughness", material.roughness],
|
||||
["metallic", material.metallic],
|
||||
["normalStrength", material.normalStrength]
|
||||
] as const) {
|
||||
if (!isFiniteNumberInRange(value, 0, 1)) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-scalar",
|
||||
"Custom material scalar values must be between 0 and 1.",
|
||||
`${path}.${property}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const slot of MATERIAL_TEXTURE_SLOTS) {
|
||||
validateCustomMaterialTextureRef(material, slot, assets, path, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
function validateMaterialDef(
|
||||
material: MaterialDef,
|
||||
path: string,
|
||||
assets: Record<string, ProjectAssetRecord>,
|
||||
diagnostics: SceneDiagnostic[]
|
||||
) {
|
||||
if (material.kind !== "starter" && material.kind !== "custom") {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-kind",
|
||||
"Material kind must be starter or custom.",
|
||||
`${path}.kind`
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isHexColorString(material.swatchColorHex)) {
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
"error",
|
||||
"invalid-material-swatch-color",
|
||||
"Material swatch color must use #RRGGBB format.",
|
||||
`${path}.swatchColorHex`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
validateMaterialTags(material.tags, path, diagnostics);
|
||||
|
||||
if (material.kind === "starter") {
|
||||
validateStarterMaterialDef(material, path, diagnostics);
|
||||
} else {
|
||||
validateCustomMaterialDef(material, path, assets, diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
function filterProjectSceneDiagnostics(
|
||||
diagnostics: SceneDiagnostic[]
|
||||
): SceneDiagnostic[] {
|
||||
|
||||
Reference in New Issue
Block a user