P7/P8: add topology proxies and recovery reports

This commit is contained in:
2026-08-02 17:24:50 -04:00
parent 4cc349a589
commit 9c76f33f3d
11 changed files with 156 additions and 16 deletions

View File

@@ -1,5 +1,6 @@
import { unzipSync } from 'fflate'
import { XMLParser } from 'fast-xml-parser'
import type { DocumentSnapshot, DocumentObjectSnapshot, ModelTreeItem, ObjectPropertySnapshot } from './types'
export type FcstdArchiveLimits = {
maxArchiveBytes: number
@@ -47,6 +48,7 @@ export type FcstdInspection = {
entries: FcstdEntryMetadata[]
objects: FcstdObjectSummary[]
compatibility: FcstdCompatibilityReport
proxyDocument: DocumentSnapshot
}
export const DEFAULT_FCSTD_LIMITS: FcstdArchiveLimits = {
@@ -72,6 +74,12 @@ const recognizedTypeIds = new Set([
'Sketcher::SketchObject',
])
const hashString = (value: string) => {
let hash = 2166136261
for (let index = 0; index < value.length; index += 1) hash = Math.imul(hash ^ value.charCodeAt(index), 16777619)
return (hash >>> 0).toString(16).padStart(8, '0')
}
const blockedTypeId = (typeId: string) => /(?:FeaturePython|PythonFeature|::Python)/i.test(typeId)
const entryRole = (path: string): FcstdEntryRole => {
@@ -203,6 +211,15 @@ const parseDocumentXml = (bytes: Uint8Array) => {
}
}
const createProxyProperty = (name: string, label: string, value: string): ObjectPropertySnapshot => ({ name, label, group: 'FCStd import', scope: 'data', type: 'App::PropertyString', value, readOnly: true })
const createProxyDocument = (inspection: Omit<FcstdInspection, 'proxyDocument'>): DocumentSnapshot => {
const documentId = `fcstd-${hashString(`${inspection.label}|${inspection.schemaVersion}|${inspection.objects.map((object) => object.name).join('|')}`)}`
const tree: ModelTreeItem[] = inspection.objects.map((object) => ({ id: object.name, label: object.label, type: object.typeId.includes('Body') ? 'body' : object.typeId.includes('Sketch') ? 'sketch' : 'feature', state: 'readonly', detail: `${object.typeId} · ${object.support}` }))
const objects: DocumentObjectSnapshot[] = inspection.objects.map((object) => ({ id: object.name, typeId: object.typeId, properties: [createProxyProperty('Label', 'Label', object.label), createProxyProperty('TypeId', 'Type', object.typeId), createProxyProperty('ImportSupport', 'Import support', object.support), createProxyProperty('PropertyCount', 'Property count', String(object.propertyCount))] }))
return { id: documentId, label: inspection.label, version: 1, dirty: false, readOnly: true, units: 'mm', tree, objects, dependencies: [], recompute: { generation: 0, status: 'idle', objectStates: Object.fromEntries(objects.map((object) => [object.id, 'up-to-date'])), dirtyObjects: [], order: [], errors: [] } }
}
export const inspectFcstdArchive = (bytes: Uint8Array, limitOverrides: Partial<FcstdArchiveLimits> = {}): FcstdInspection => {
const limits = { ...DEFAULT_FCSTD_LIMITS, ...limitOverrides }
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`FCStd limit ${name} must be a positive safe integer.`)
@@ -223,7 +240,7 @@ export const inspectFcstdArchive = (bytes: Uint8Array, limitOverrides: Partial<F
const proxyObjects = document.objects.filter((object) => object.support === 'proxy')
if (proxyObjects.length > 0) warnings.push(`${proxyObjects.length} unrecognized object type(s) require a read-only proxy.`)
const level: FcstdCompatibilityReport['level'] = blockedObjects.length > 0 ? 'blocked' : proxyObjects.length > 0 || scriptEntries.length > 0 ? 'partial' : 'metadata-compatible'
return {
const inspection: Omit<FcstdInspection, 'proxyDocument'> = {
format: 'FCStd',
schemaVersion: document.schemaVersion,
label: document.label,
@@ -240,4 +257,5 @@ export const inspectFcstdArchive = (bytes: Uint8Array, limitOverrides: Partial<F
warnings,
},
}
return { ...inspection, proxyDocument: createProxyDocument(inspection) }
}