Files
workinf_Blender_Wasm/web/protocol/paint-pbvh-capability.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

151 lines
6.2 KiB
TypeScript

import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } from "./capability-gates";
import type { ErrorCode } from "./error";
export const PAINT_PBVH_CAPABILITY_SCHEMA = 1 as const;
export const PAINT_PBVH_WASM_ENTRYPOINT = "_web_engine_apply_pbvh_stroke" as const;
export type PaintPBVHDomain = "SCULPT" | "VERTEX_COLOR" | "WEIGHT" | "TEXTURE";
const BRUSH_INVENTORY = {
SCULPT: [
"DRAW", "SMOOTH", "PINCH", "INFLATE", "GRAB", "LAYER", "CLAY", "NUDGE", "THUMB",
"SNAKE_HOOK", "ROTATE", "SIMPLIFY", "CREASE", "BLOB", "CLAY_STRIPS", "MASK",
"DRAW_SHARP", "ELASTIC_DEFORM", "POSE", "MULTIPLANE_SCRAPE", "SLIDE_RELAX",
"CLAY_THUMB", "CLOTH", "DRAW_FACE_SETS", "PAINT", "SMEAR", "BOUNDARY",
"DISPLACEMENT_ERASER", "DISPLACEMENT_SMEAR", "PLANE", "BLUR", "SCENE_PROJECT",
],
VERTEX_COLOR: ["DRAW", "BLUR", "AVERAGE", "SMEAR"],
WEIGHT: ["DRAW", "BLUR", "AVERAGE", "SMEAR"],
TEXTURE: ["DRAW", "SOFTEN", "SMEAR", "CLONE", "FILL", "MASK"],
} as const satisfies Record<PaintPBVHDomain, readonly string[]>;
export interface PaintPBVHBrushInventoryEntry {
domain: PaintPBVHDomain;
brush: string;
source: "blender-5.2.0/source/blender/makesdna/DNA_brush_enums.h";
}
export interface PaintPBVHCapabilityRequest {
schemaVersion: typeof PAINT_PBVH_CAPABILITY_SCHEMA;
operation: "PBVH_BRUSH";
domain: PaintPBVHDomain;
brush: string;
objectId: string;
meshId: string;
baseRevision: number;
}
export interface PaintPBVHCapabilityContext {
nativeEntrypointPresent: boolean;
sessionContextReady: boolean;
verifiedBrushes: ReadonlySet<string>;
currentRevision?: number;
currentObjectId?: string;
currentMeshId?: string;
}
export class PaintPBVHCapabilityError extends Error {
readonly code: ErrorCode;
readonly path?: string;
constructor(code: ErrorCode, message: string, path?: string) {
super(message);
this.name = "PaintPBVHCapabilityError";
this.code = code;
this.path = path;
}
}
function fail(code: ErrorCode, message: string, path?: string): never {
throw new PaintPBVHCapabilityError(code, message, path);
}
function record(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stableId(value: unknown, prefix: "object:" | "mesh:", path: string): string {
if (typeof value !== "string" || !value.startsWith(prefix) || value.length <= prefix.length || value.length > 512) {
fail("PAINT_SCHEMA_INVALID", `${path} must be a bounded ${prefix.slice(0, -1)} stable ID`, path);
}
return value;
}
export function paintPBVHBrushInventory(): PaintPBVHBrushInventoryEntry[] {
return (Object.entries(BRUSH_INVENTORY) as Array<[PaintPBVHDomain, readonly string[]]>).flatMap(([domain, brushes]) =>
brushes.map((brush) => ({
domain,
brush,
source: "blender-5.2.0/source/blender/makesdna/DNA_brush_enums.h" as const,
})),
);
}
export function parsePaintPBVHCapabilityRequest(value: unknown): PaintPBVHCapabilityRequest {
if (!record(value)) fail("PAINT_SCHEMA_INVALID", "PBVH capability request must be an object");
const allowed = ["schemaVersion", "operation", "domain", "brush", "objectId", "meshId", "baseRevision"];
const extra = Object.keys(value).find((key) => !allowed.includes(key));
if (extra) fail("PAINT_SCHEMA_INVALID", `PBVH capability request contains unsupported field ${extra}`, extra);
if (value.schemaVersion !== PAINT_PBVH_CAPABILITY_SCHEMA) fail("PROTOCOL_MISMATCH", "Unsupported PBVH capability schema", "schemaVersion");
if (value.operation !== "PBVH_BRUSH") fail("PAINT_SCHEMA_INVALID", "operation must be PBVH_BRUSH", "operation");
if (typeof value.domain !== "string" || !(value.domain in BRUSH_INVENTORY)) {
fail("PAINT_SCHEMA_INVALID", "domain is not a PBVH paint domain", "domain");
}
const domain = value.domain as PaintPBVHDomain;
if (typeof value.brush !== "string" || !(BRUSH_INVENTORY[domain] as readonly string[]).includes(value.brush)) {
fail("PAINT_PBVH_BRUSH_UNVERIFIED", `Brush ${String(value.brush)} is not in the Blender 5.2 ${domain} inventory`, "brush");
}
if (!Number.isSafeInteger(value.baseRevision) || (value.baseRevision as number) < 0) {
fail("PAINT_SCHEMA_INVALID", "baseRevision must be a non-negative safe integer", "baseRevision");
}
return {
schemaVersion: PAINT_PBVH_CAPABILITY_SCHEMA,
operation: "PBVH_BRUSH",
domain,
brush: value.brush,
objectId: stableId(value.objectId, "object:", "objectId"),
meshId: stableId(value.meshId, "mesh:", "meshId"),
baseRevision: value.baseRevision as number,
};
}
export function gatePaintPBVHCapability(
value: unknown,
context: PaintPBVHCapabilityContext,
): CapabilityGateResult {
const request = parsePaintPBVHCapabilityRequest(value);
const capability = `PBVH_${request.domain}_${request.brush}`;
if (context.currentRevision !== undefined && request.baseRevision !== context.currentRevision) {
return blockedGate("N-017", capability, [
capabilityIssue("REVISION_CONFLICT", "PBVH capability request is stale", "baseRevision"),
]);
}
if ((context.currentObjectId !== undefined && request.objectId !== context.currentObjectId) ||
(context.currentMeshId !== undefined && request.meshId !== context.currentMeshId)) {
return blockedGate("N-017", capability, [
capabilityIssue("PAINT_SCHEMA_INVALID", "PBVH request does not target the current Mesh object", "objectId"),
]);
}
if (!context.nativeEntrypointPresent) {
return blockedGate("N-017", capability, [
capabilityIssue(
"PAINT_PBVH_UNAVAILABLE",
`${PAINT_PBVH_WASM_ENTRYPOINT} is not present in the WebEngine WASM build`,
"operation",
false,
),
]);
}
if (!context.sessionContextReady) {
return blockedGate("N-017", capability, [
capabilityIssue("PAINT_PBVH_CONTEXT_UNAVAILABLE", "The Blender PBVH paint session context is not initialized", "operation"),
]);
}
if (!context.verifiedBrushes.has(`${request.domain}:${request.brush}`)) {
return blockedGate("N-017", capability, [
capabilityIssue("PAINT_PBVH_BRUSH_UNVERIFIED", "This PBVH brush has no desktop/WASM golden", "brush"),
]);
}
return readyGate("N-017", capability);
}