Cache heading pin startup scan

This commit is contained in:
flash555588 2026-06-27 07:34:40 +08:00
parent 0d9a38d244
commit 0a72194f61
5 changed files with 627 additions and 590 deletions

View file

@ -21,6 +21,7 @@
- Performance: skip full Live Preview line parsing for editor documents that do not contain model embed markers, reducing startup work for large ordinary notes.
- Performance: defer Three.js mesh shadow flag setup and shadow-map updates until ground shadows or shadow-casting lights are active, reducing large-model load work on the default route.
- Performance: keep the heading-pin DOM observer disabled until at least one annotation is bound to a note heading, avoiding startup-wide heading scans in vaults without heading-linked model pins.
- Performance: cache heading-linked annotation detection by model-profile table identity so unrelated store updates do not rescan every saved profile before the observer starts.
- Performance: compact saved registered-part mesh references to representative samples, reducing `data.json` size and startup parsing work for large converted assemblies while preserving component identity fields.
- Performance: compact persisted registered-part bounding boxes and centers to stable significant digits, removing floating-point tails from `data.json` without dropping tiny-part scale precision.
- Performance: strip derived automatic registered-part observation text from persisted profiles while preserving structured component, format-lineage, material, count, and reviewed-note fields.

1166
main.js

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
import { Notice, Plugin, TFile } from "obsidian";
import type { AnnotationPin, PluginSettings } from "./domain/models";
import type { AnnotationPin, ModelAssetProfile, PluginSettings } from "./domain/models";
import { createConvertedAssetCache, type ConvertedAssetCache } from "./io/cache/converted-asset-cache";
import { listSupportedModelExtensions, isSupportedModelExtension } from "./io/formats/registry";
import { createPluginStore, type PluginStore } from "./store/plugin-store";
@ -8,6 +8,7 @@ import { DIRECT_VIEW_TYPE } from "./view/direct-view-type";
import { LazyAI3DSettingTab } from "./lazy-setting-tab";
import { createLogger, setLogLevel } from "./utils/log";
import { formatT, setLocale, t, type Locale } from "./i18n";
import { containsHeadingLinkedAnnotations } from "./view/heading-pin-map";
const log = createLogger("main");
const POST_LAYOUT_STARTUP_DELAY_MS = 700;
@ -77,6 +78,8 @@ export default class AI3DModelWorkbench extends Plugin {
private convertedAssetCache!: ConvertedAssetCache;
private unloaded = false;
private headingPinObserverStarted = false;
private headingLinkedProfilesRef: Record<string, ModelAssetProfile> | null = null;
private headingLinkedProfilesResult = false;
getSettings(): PluginSettings {
return this.ps.store.getState().settings;
@ -214,12 +217,12 @@ export default class AI3DModelWorkbench extends Plugin {
private hasHeadingLinkedAnnotations(): boolean {
const profiles = this.ps.store.getState().modelAssetProfiles;
for (const profile of Object.values(profiles)) {
if (profile.annotations.some((pin) => typeof pin.headingRef === "string" && pin.headingRef.trim().length > 0)) {
return true;
}
if (this.headingLinkedProfilesRef === profiles) {
return this.headingLinkedProfilesResult;
}
return false;
this.headingLinkedProfilesRef = profiles;
this.headingLinkedProfilesResult = containsHeadingLinkedAnnotations(profiles);
return this.headingLinkedProfilesResult;
}
private async registerLivePreviewExtension(getAnnotations: (modelPath: string) => AnnotationPin[]): Promise<void> {

View file

@ -22,3 +22,14 @@ export function buildHeadingPinMap(profiles: Record<string, ModelAssetProfile>):
}
return map;
}
export function containsHeadingLinkedAnnotations(profiles: Record<string, ModelAssetProfile>): boolean {
for (const profile of Object.values(profiles)) {
for (const pin of profile.annotations) {
if (typeof pin.headingRef === "string" && normalizeHeadingText(pin.headingRef)) {
return true;
}
}
}
return false;
}

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { ModelAssetProfile } from "../domain/models";
import { buildHeadingPinMap } from "./heading-pin-map";
import { buildHeadingPinMap, containsHeadingLinkedAnnotations } from "./heading-pin-map";
function profile(annotations: ModelAssetProfile["annotations"]): ModelAssetProfile {
return {
@ -40,3 +40,25 @@ describe("buildHeadingPinMap", () => {
]);
});
});
describe("containsHeadingLinkedAnnotations", () => {
it("detects normalized non-empty heading refs", () => {
expect(containsHeadingLinkedAnnotations({
"models/a.glb": profile([
{ id: "plain", position: [0, 0, 0], label: "Plain", color: "#fff", headingRef: " ", createdAt: "now" },
]),
"models/b.glb": profile([
{ id: "pin-b", position: [1, 0, 0], label: "B", color: "#0f0", headingRef: " Motor Housing ", createdAt: "now" },
]),
})).toBe(true);
});
it("skips profiles without heading refs", () => {
expect(containsHeadingLinkedAnnotations({
"models/a.glb": profile([
{ id: "plain", position: [0, 0, 0], label: "Plain", color: "#fff", createdAt: "now" },
]),
"models/b.glb": profile([]),
})).toBe(false);
});
});