Normalize oversized schema-marked part profiles

This commit is contained in:
flash555588 2026-06-27 06:34:49 +08:00
parent afe4787fe4
commit eb3226871e
4 changed files with 224 additions and 161 deletions

View file

@ -25,6 +25,7 @@
- Performance: lazy-load the full settings tab UI and model import modal so normal workspace startup only registers lightweight entry points.
- Performance: combine model profile normalization and compact-state change detection into one pass, reducing startup work for large `data.json` registered-part lists.
- Performance: reuse already-normalized registered part arrays while loading model profiles, avoiding repeated object allocation for compact `data.json` state.
- Performance: normalize schema-marked but oversized registered-part lists on load so anomalous large `data.json` profiles are compacted back under the startup budget.
- Performance: cache Three.js root bounds and pre-index grouped part descendants so large converted models do less repeated scene traversal after loading.
- Performance: reuse Three.js child-mesh descendant indexes for picking, evidence grouping, and disassembly setup so large assemblies avoid repeated subtree scans during selection.
- Performance: reuse shared Three.js focus-dim materials across meshes with the same source material, reducing material churn when focusing parts in large assemblies.

320
main.js

File diff suppressed because one or more lines are too long

View file

@ -370,6 +370,65 @@ describe("createPluginStore persistence", () => {
expect(saveData).not.toHaveBeenCalled();
});
it("normalizes schema-marked profiles that still contain oversized registered part lists", async () => {
const now = "2026-06-22T00:00:00.000Z";
const createPart = (index: number, partial: Partial<PartRecord> = {}): PartRecord => ({
partId: `part-${index}`,
assetId: "models/oversized.glb",
name: `mesh-${index}`,
source: "mesh",
meshRefs: [`mesh-${index}`],
materialRefs: [],
confidence: 0.2,
observations: [],
inferredFunctions: [],
knowledgeTags: [],
reviewed: false,
...partial,
});
const saved: PersistedPluginState = {
stateSchemaVersion: 1,
settings: { ...DEFAULT_SETTINGS },
convertedAssetRecords: [],
modelAssetProfiles: {
"models/oversized.glb": {
tags: [],
notes: "",
annotations: [],
registeredParts: [
...Array.from({ length: 300 }, (_value, index) => createPart(index)),
createPart(1000, { partId: "reviewed-part", name: "Reviewed part", reviewed: true }),
createPart(1001, { partId: "component-part", name: "Component", source: "component", confidence: 0.82 }),
],
createdAt: now,
updatedAt: now,
},
},
agentDraft: "",
agentPlan: null,
lastKnowledgeGeneration: null,
};
const normalizedSaves: PersistedPluginState[] = [];
const { plugin, saveData } = createFakePlugin(async (data) => {
normalizedSaves.push(data);
}, saved);
const pluginStore = createPluginStore(plugin);
await pluginStore.load();
const parts = pluginStore.store.getState().modelAssetProfiles["models/oversized.glb"]?.registeredParts ?? [];
expect(parts).toHaveLength(256);
expect(parts.some((part) => part.partId === "reviewed-part")).toBe(true);
expect(parts.some((part) => part.partId === "component-part")).toBe(true);
await vi.advanceTimersByTimeAsync(50);
await settlePromises();
expect(saveData).toHaveBeenCalledTimes(1);
expect(normalizedSaves[0].stateSchemaVersion).toBe(1);
expect(normalizedSaves[0].modelAssetProfiles["models/oversized.glb"]?.registeredParts).toHaveLength(256);
});
it("persists the current schema marker once for legacy compact state", async () => {
const now = "2026-06-22T00:00:00.000Z";
const saved: PersistedPluginState = {

View file

@ -240,7 +240,10 @@ function isReusableModelAssetProfile(profile: Partial<ModelAssetProfile>): profi
return Array.isArray(profile.tags) &&
typeof profile.notes === "string" &&
Array.isArray(profile.annotations) &&
(profile.registeredParts === undefined || Array.isArray(profile.registeredParts)) &&
(
profile.registeredParts === undefined ||
(Array.isArray(profile.registeredParts) && profile.registeredParts.length <= MAX_REGISTERED_PARTS_PER_PROFILE)
) &&
isNormalizedOptionalString(profile.analysisVersion) &&
isNormalizedOptionalString(profile.reportNotePath) &&
isNormalizedOptionalString(profile.analysisSidecarPath) &&