feat: prepare FreeCAD private naming worker linkage
This commit is contained in:
@@ -52,6 +52,7 @@ export type FcstdObjectSummary = {
|
||||
name: string
|
||||
label: string
|
||||
typeId: string
|
||||
nativeObjectTag?: number
|
||||
propertyCount: number
|
||||
properties: FcstdPropertySummary[]
|
||||
support: FcstdObjectSupport
|
||||
@@ -1103,16 +1104,32 @@ export const serializeFcstdMetadataArchive = (document: DocumentSnapshot, option
|
||||
const objectIds = document.objects.map((object) => object.id.trim())
|
||||
if (objectIds.some((objectId) => !objectId)) throw new TypeError('FCStd metadata writer requires non-empty object ids.')
|
||||
if (new Set(objectIds).size !== objectIds.length) throw new TypeError('FCStd metadata writer requires unique object ids.')
|
||||
const explicitObjectTags = new Set<number>()
|
||||
for (const object of document.objects) {
|
||||
if (object.nativeObjectTag === undefined) continue
|
||||
if (!Number.isSafeInteger(object.nativeObjectTag) || object.nativeObjectTag <= 0) throw new RangeError(`FCStd object ${object.id} nativeObjectTag must be a positive safe integer.`)
|
||||
if (explicitObjectTags.has(object.nativeObjectTag)) throw new RangeError(`FCStd nativeObjectTag ${object.nativeObjectTag} is assigned to more than one object.`)
|
||||
explicitObjectTags.add(object.nativeObjectTag)
|
||||
}
|
||||
let nextObjectTag = 1
|
||||
const objectTags = new Map(document.objects.map((object) => {
|
||||
if (object.nativeObjectTag !== undefined) return [object.id, object.nativeObjectTag] as const
|
||||
while (explicitObjectTags.has(nextObjectTag)) nextObjectTag += 1
|
||||
const tag = nextObjectTag
|
||||
explicitObjectTags.add(tag)
|
||||
nextObjectTag += 1
|
||||
return [object.id, tag] as const
|
||||
}))
|
||||
const objectIdSet = new Set(objectIds)
|
||||
const dependencies = new Map(objectIds.map((objectId) => [objectId, [] as string[]]))
|
||||
for (const edge of document.dependencies ?? []) {
|
||||
if (!objectIdSet.has(edge.sourceId) || !objectIdSet.has(edge.targetId)) throw new Error(`FCStd dependency ${edge.sourceId} -> ${edge.targetId} references an undeclared object.`)
|
||||
if (edge.relation !== 'view') dependencies.get(edge.sourceId)?.push(edge.targetId)
|
||||
}
|
||||
const objectDeclarations = document.objects.map((object, objectIndex) => {
|
||||
const objectDeclarations = document.objects.map((object) => {
|
||||
const targets = dependencies.get(object.id) ?? []
|
||||
const dependencyXml = targets.length === 0 ? `<ObjectDeps Name="${xmlEscape(object.id)}" Count="0"/>` : `<ObjectDeps Name="${xmlEscape(object.id)}" Count="${targets.length}">${targets.map((target) => `<Dep Name="${xmlEscape(target)}"/>`).join('')}</ObjectDeps>`
|
||||
return `${dependencyXml}<Object name="${xmlEscape(object.id)}" type="${xmlEscape(nativeFcstdTypeId(object.typeId))}" id="${objectIndex + 1}" Touched="1"/>`
|
||||
return `${dependencyXml}<Object name="${xmlEscape(object.id)}" type="${xmlEscape(nativeFcstdTypeId(object.typeId))}" id="${objectTags.get(object.id)}" Touched="1"/>`
|
||||
}).join('')
|
||||
const objectData = document.objects.map((object) => {
|
||||
const sketchGeneratedPropertyNames = object.sketch ? new Set(['Geometry', 'Constraints', 'ExternalGeo', 'ExternalGeometry', 'ExternalTypes', 'WebGeometryIds', 'WebSyntheticConstraintIds', 'WebExternalIds', 'WebExternalProjectionIds', 'WebExternalSources']) : new Set<string>()
|
||||
@@ -2014,9 +2031,18 @@ const parseDocumentXml = (bytes: Uint8Array, limits: FcstdArchiveLimits) => {
|
||||
const dataByName = new Map(objectData.map((data) => [attribute(data, 'name'), data]))
|
||||
const documentProperties = asArray((((root.Properties as Record<string, unknown> | undefined)?.Property) as Record<string, unknown> | Record<string, unknown>[] | undefined))
|
||||
const labelProperty = documentProperties.find((property) => attribute(property, 'name') === 'Label')
|
||||
const nativeObjectTags = new Set<number>()
|
||||
const objects = objectDeclarations.map((declaration): FcstdObjectSummary => {
|
||||
const name = attribute(declaration, 'name') || '<unnamed>'
|
||||
const typeId = attribute(declaration, 'type') || 'App::DocumentObject'
|
||||
const objectTagText = attribute(declaration, 'id')
|
||||
let nativeObjectTag: number | undefined
|
||||
if (objectTagText) {
|
||||
if (!/^\d+$/.test(objectTagText) || !Number.isSafeInteger(Number(objectTagText)) || Number(objectTagText) <= 0) throw new Error(`FCStd object ${name} has an invalid native id.`)
|
||||
nativeObjectTag = Number(objectTagText)
|
||||
if (nativeObjectTags.has(nativeObjectTag)) throw new Error(`FCStd object native id ${nativeObjectTag} is duplicated.`)
|
||||
nativeObjectTags.add(nativeObjectTag)
|
||||
}
|
||||
const data = dataByName.get(name)
|
||||
const propertiesContainer = data?.Properties as Record<string, unknown> | undefined
|
||||
const properties = asArray(((propertiesContainer?.Property) as Record<string, unknown> | Record<string, unknown>[] | undefined))
|
||||
@@ -2029,7 +2055,7 @@ const parseDocumentXml = (bytes: Uint8Array, limits: FcstdArchiveLimits) => {
|
||||
const support: FcstdObjectSupport = blockedTypeId(typeId) ? 'blocked' : recognizedTypeIds.has(typeId) ? 'recognized' : 'proxy'
|
||||
const summaries = propertySummaries(properties).map((property) => expressions.has(property.name) ? { ...property, expression: expressions.get(property.name) } : property)
|
||||
const sketch = typeId === 'Sketcher::SketchObject' ? sketchFromPropertySummaries(name, summaries) : undefined
|
||||
return { name, label: objectLabelProperty ? propertyValue(objectLabelProperty) || name : name, typeId, propertyCount: properties.length, properties: summaries, support, extensions, ...(sketch ? { sketch } : {}) }
|
||||
return { name, label: objectLabelProperty ? propertyValue(objectLabelProperty) || name : name, typeId, ...(nativeObjectTag === undefined ? {} : { nativeObjectTag }), propertyCount: properties.length, properties: summaries, support, extensions, ...(sketch ? { sketch } : {}) }
|
||||
})
|
||||
return {
|
||||
schemaVersion: attribute(root, 'SchemaVersion') || attribute(root, 'schemaVersion') || 'unknown',
|
||||
@@ -2348,6 +2374,7 @@ const createProxyDocument = (inspection: Omit<FcstdInspection, 'proxyDocument'>)
|
||||
return {
|
||||
id: object.name,
|
||||
typeId: object.typeId,
|
||||
...(object.nativeObjectTag === undefined ? {} : { nativeObjectTag: object.nativeObjectTag }),
|
||||
properties: [
|
||||
createProxyProperty('Label', 'Label', object.label),
|
||||
createProxyProperty('TypeId', 'Type', object.typeId),
|
||||
|
||||
@@ -1324,9 +1324,12 @@ export class BitbybitGeometryRuntime {
|
||||
const stageTransport = input.stages?.length ? {
|
||||
inputs: input.inputs.map((source, index) => ({
|
||||
inputId: source.inputId ?? `${input.operationId}:input:${index}`,
|
||||
objectId: source.objectId,
|
||||
role: source.role,
|
||||
stageId: source.stageId,
|
||||
step: stepByObjectId.get(source.objectId)!.text,
|
||||
objectTag: source.objectTag,
|
||||
namingEvidence: source.namingEvidence,
|
||||
})),
|
||||
stages: input.stages.map((stage) => ({
|
||||
stageId: stage.stageId,
|
||||
@@ -1339,12 +1342,12 @@ export class BitbybitGeometryRuntime {
|
||||
ordinal: stage.ordinal,
|
||||
})),
|
||||
} : {}
|
||||
const captureHistory = (request: Parameters<NativeOcctHistoryCoordinator['capture']>[1]) => nativeHistory.coordinator.capture(nativeHistory.provider, { ...request, ...stageTransport })
|
||||
const mapHistoryRecords = (response: Parameters<typeof mapNativeOcctHistoryRecords>[0], sourceIds: { object: string; tool: string } | Record<string, string>) => mapNativeOcctHistoryRecords(response, sourceIds, input)
|
||||
type NativeStageRequest = Pick<NativeOcctHistoryRequest, 'objectStep'> & Partial<Pick<NativeOcctHistoryRequest, 'toolStep' | 'direction' | 'axisOrigin' | 'angle'>>
|
||||
const finalResultObjectId = () => input.stages?.length
|
||||
? input.stages[input.stages.length - 1].resultObjectId ?? `${input.operationId}:result`
|
||||
: `${input.operationId}:result`
|
||||
const captureHistory = (request: Parameters<NativeOcctHistoryCoordinator['capture']>[1]) => nativeHistory.coordinator.capture(nativeHistory.provider, { ...request, ...stageTransport, resultObjectId: finalResultObjectId(), resultObjectTag: input.resultObjectTag })
|
||||
const mapHistoryRecords = (response: Parameters<typeof mapNativeOcctHistoryRecords>[0], sourceIds: { object: string; tool: string } | Record<string, string>) => mapNativeOcctHistoryRecords(response, sourceIds, input)
|
||||
type NativeStageRequest = Pick<NativeOcctHistoryRequest, 'objectStep'> & Partial<Pick<NativeOcctHistoryRequest, 'toolStep' | 'direction' | 'axisOrigin' | 'angle'>>
|
||||
const namingEvidenceForResponse = (response: NativeOcctHistoryResponse, stageId: string, resultObjectId: string) => response.namingEvidence
|
||||
? assertNativeNamingEvidence({ ...response.namingEvidence, stageId, resultObjectId })
|
||||
: createFinalShapeOnlyNamingEvidence(stageId, resultObjectId)
|
||||
@@ -1358,11 +1361,24 @@ export class BitbybitGeometryRuntime {
|
||||
inputs: NativeTopologyHistoryInput['inputs']
|
||||
request: NativeStageRequest
|
||||
}) => {
|
||||
const transportInputs = stage.inputs.map((source, index) => ({
|
||||
inputId: `${stage.stageId}:input:${index}`,
|
||||
objectId: source.objectId,
|
||||
role: index === 0 ? 'object' : 'tool',
|
||||
stageId: source.stageId,
|
||||
step: index === 0 ? stage.request.objectStep : stage.request.toolStep ?? stage.request.objectStep,
|
||||
objectTag: source.objectTag,
|
||||
namingEvidence: source.namingEvidence,
|
||||
}))
|
||||
const execution = await nativeHistory.coordinator.capture(nativeHistory.provider, {
|
||||
documentId: input.documentId,
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: stage.stageId,
|
||||
operation: stage.operation,
|
||||
resultObjectId: stage.resultObjectId,
|
||||
...(stage.resultObjectId === finalResultObjectId() && input.resultObjectTag !== undefined ? { resultObjectTag: input.resultObjectTag } : {}),
|
||||
inputs: transportInputs,
|
||||
stages: [{ stageId: stage.stageId, operation: stage.operation, inputIds: transportInputs.map(({ inputId }) => inputId), ordinal: stage.ordinal }],
|
||||
...stage.request,
|
||||
})
|
||||
if (execution.status !== 'completed' || !execution.response) throw new Error(`Native OCCT ${stage.operation} stage history ${execution.status}.`)
|
||||
@@ -1445,8 +1461,8 @@ export class BitbybitGeometryRuntime {
|
||||
ordinal: 2,
|
||||
sourceIds: { object: firstObjectId, tool: secondObjectId },
|
||||
inputs: [
|
||||
{ objectId: firstObjectId, shape: profileInput.shape, stageId: firstStageId },
|
||||
{ objectId: secondObjectId, shape: profileInput.shape, stageId: secondStageId },
|
||||
{ objectId: firstObjectId, shape: profileInput.shape, stageId: firstStageId, namingEvidence: first.capture.namingEvidence },
|
||||
{ objectId: secondObjectId, shape: profileInput.shape, stageId: secondStageId, namingEvidence: second.capture.namingEvidence },
|
||||
],
|
||||
request: { objectStep: first.resultStep, toolStep: second.resultStep },
|
||||
})
|
||||
@@ -1498,8 +1514,8 @@ export class BitbybitGeometryRuntime {
|
||||
ordinal: 2,
|
||||
sourceIds: { object: toolOneObjectId, tool: toolTwoObjectId },
|
||||
inputs: [
|
||||
{ objectId: toolOneObjectId, shape: profileInput.shape, stageId: toolOneStageId },
|
||||
{ objectId: toolTwoObjectId, shape: profileInput.shape, stageId: toolTwoStageId },
|
||||
{ objectId: toolOneObjectId, shape: profileInput.shape, stageId: toolOneStageId, namingEvidence: toolOne.capture.namingEvidence },
|
||||
{ objectId: toolTwoObjectId, shape: profileInput.shape, stageId: toolTwoStageId, namingEvidence: toolTwo.capture.namingEvidence },
|
||||
],
|
||||
request: { objectStep: toolOne.resultStep, toolStep: toolTwo.resultStep },
|
||||
})
|
||||
@@ -1512,7 +1528,7 @@ export class BitbybitGeometryRuntime {
|
||||
sourceIds: { object: baseInput.objectId, tool: fusedToolObjectId },
|
||||
inputs: [
|
||||
baseInput,
|
||||
{ objectId: fusedToolObjectId, shape: profileInput.shape, stageId: fuseStageId },
|
||||
{ objectId: fusedToolObjectId, shape: profileInput.shape, stageId: fuseStageId, namingEvidence: fusedTool.capture.namingEvidence },
|
||||
],
|
||||
request: { objectStep: baseStep, toolStep: fusedTool.resultStep },
|
||||
})
|
||||
@@ -1554,7 +1570,7 @@ export class BitbybitGeometryRuntime {
|
||||
resultObjectId: finalResultObjectId(),
|
||||
ordinal: 1,
|
||||
sourceIds: { object: rotatedProfileObjectId, tool: rotatedProfileObjectId },
|
||||
inputs: [{ objectId: rotatedProfileObjectId, shape: profileInput.shape, stageId: rotateStageId }],
|
||||
inputs: [{ objectId: rotatedProfileObjectId, shape: profileInput.shape, stageId: rotateStageId, namingEvidence: rotated.capture.namingEvidence }],
|
||||
request: { objectStep: rotated.resultStep, axisOrigin, direction: parameters.direction, angle: parameters.totalAngle },
|
||||
})
|
||||
return withStageCaptures([rotated.capture, revolution.capture])
|
||||
@@ -1592,7 +1608,7 @@ export class BitbybitGeometryRuntime {
|
||||
resultObjectId: revolutionToolObjectId,
|
||||
ordinal: 1,
|
||||
sourceIds: { object: rotatedProfileObjectId, tool: rotatedProfileObjectId },
|
||||
inputs: [{ objectId: rotatedProfileObjectId, shape: profileInput.shape, stageId: rotateStageId }],
|
||||
inputs: [{ objectId: rotatedProfileObjectId, shape: profileInput.shape, stageId: rotateStageId, namingEvidence: rotated.capture.namingEvidence }],
|
||||
request: { objectStep: rotated.resultStep, axisOrigin, direction: parameters.direction, angle: parameters.totalAngle },
|
||||
})
|
||||
const cut = await captureStage({
|
||||
@@ -1602,7 +1618,7 @@ export class BitbybitGeometryRuntime {
|
||||
resultObjectId: finalResultObjectId(),
|
||||
ordinal: 2,
|
||||
sourceIds: { object: baseInput.objectId, tool: revolutionToolObjectId },
|
||||
inputs: [baseInput, { objectId: revolutionToolObjectId, shape: profileInput.shape, stageId: revolutionStageId }],
|
||||
inputs: [baseInput, { objectId: revolutionToolObjectId, shape: profileInput.shape, stageId: revolutionStageId, namingEvidence: revolution.capture.namingEvidence }],
|
||||
request: { objectStep: baseStep, toolStep: revolution.resultStep },
|
||||
})
|
||||
return withStageCaptures([rotated.capture, revolution.capture, cut.capture])
|
||||
@@ -1639,6 +1655,9 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: toolStageId,
|
||||
operation: 'pad',
|
||||
resultObjectId: toolObjectId,
|
||||
inputs: [{ inputId: `${toolStageId}:input:0`, objectId: profileInput.objectId, role: 'object', step: profileStep.text, objectTag: profileInput.objectTag, namingEvidence: profileInput.namingEvidence }],
|
||||
stages: [{ stageId: toolStageId, operation: 'pad', inputIds: [`${toolStageId}:input:0`], ordinal: 0 }],
|
||||
objectStep: profileStep.text,
|
||||
direction: sides[0].direction,
|
||||
})
|
||||
@@ -1661,6 +1680,13 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: cutStageId,
|
||||
operation: 'cut',
|
||||
resultObjectId,
|
||||
...(input.resultObjectTag === undefined ? {} : { resultObjectTag: input.resultObjectTag }),
|
||||
inputs: [
|
||||
{ inputId: `${cutStageId}:input:0`, objectId: baseInput.objectId, role: 'object', step: baseStep.text, objectTag: baseInput.objectTag, namingEvidence: baseInput.namingEvidence },
|
||||
{ inputId: `${cutStageId}:input:1`, objectId: toolObjectId, role: 'tool', stageId: toolStageId, step: toolResponse.resultStep, namingEvidence: toolResponse.namingEvidence },
|
||||
],
|
||||
stages: [{ stageId: cutStageId, operation: 'cut', inputIds: [`${cutStageId}:input:0`, `${cutStageId}:input:1`], ordinal: 1 }],
|
||||
objectStep: baseStep.text,
|
||||
toolStep: toolResponse.resultStep,
|
||||
})
|
||||
@@ -1745,6 +1771,9 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: toolStageId,
|
||||
operation: 'revolution',
|
||||
resultObjectId: toolObjectId,
|
||||
inputs: [{ inputId: `${toolStageId}:input:0`, objectId: profileInput.objectId, role: 'object', step: profileStep.text, objectTag: profileInput.objectTag, namingEvidence: profileInput.namingEvidence }],
|
||||
stages: [{ stageId: toolStageId, operation: 'revolution', inputIds: [`${toolStageId}:input:0`], ordinal: 0 }],
|
||||
objectStep: profileStep.text,
|
||||
axisOrigin,
|
||||
direction: sides[0].direction,
|
||||
@@ -1769,6 +1798,13 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: cutStageId,
|
||||
operation: 'cut',
|
||||
resultObjectId,
|
||||
...(input.resultObjectTag === undefined ? {} : { resultObjectTag: input.resultObjectTag }),
|
||||
inputs: [
|
||||
{ inputId: `${cutStageId}:input:0`, objectId: baseInput.objectId, role: 'object', step: baseStep.text, objectTag: baseInput.objectTag, namingEvidence: baseInput.namingEvidence },
|
||||
{ inputId: `${cutStageId}:input:1`, objectId: toolObjectId, role: 'tool', stageId: toolStageId, step: toolResponse.resultStep, namingEvidence: toolResponse.namingEvidence },
|
||||
],
|
||||
stages: [{ stageId: cutStageId, operation: 'cut', inputIds: [`${cutStageId}:input:0`, `${cutStageId}:input:1`], ordinal: 1 }],
|
||||
objectStep: baseStep.text,
|
||||
toolStep: toolResponse.resultStep,
|
||||
})
|
||||
@@ -1909,6 +1945,13 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: cutStageId,
|
||||
operation: 'cut',
|
||||
resultObjectId,
|
||||
...(input.resultObjectTag === undefined ? {} : { resultObjectTag: input.resultObjectTag }),
|
||||
inputs: [
|
||||
{ inputId: `${cutStageId}:input:0`, objectId: baseInput.objectId, role: 'object', step: baseStep.text, objectTag: baseInput.objectTag, namingEvidence: baseInput.namingEvidence },
|
||||
{ inputId: `${cutStageId}:input:1`, objectId: toolObjectId, role: 'tool', stageId: toolStageId, step: toolStep.text, namingEvidence: createFinalShapeOnlyNamingEvidence(toolStageId, toolObjectId, 'Hole tool was synthesized by the geometry runtime; provider did not capture its builder naming evidence.') },
|
||||
],
|
||||
stages: [{ stageId: cutStageId, operation: 'cut', inputIds: [`${cutStageId}:input:0`, `${cutStageId}:input:1`], ordinal: 1 }],
|
||||
objectStep: baseStep.text,
|
||||
toolStep: toolStep.text,
|
||||
})
|
||||
@@ -2050,6 +2093,7 @@ export class BitbybitGeometryRuntime {
|
||||
let previousStep = baseStep.text
|
||||
let previousObjectId = baseInput.objectId
|
||||
let previousStageId: string | undefined
|
||||
let previousNamingEvidence = baseInput.namingEvidence
|
||||
const declaredResultObjectId = input.stages?.length ? input.stages[input.stages.length - 1].resultObjectId : undefined
|
||||
for (let index = 0; index < input.transforms.length; index += 1) {
|
||||
const step = input.transforms[index]
|
||||
@@ -2062,6 +2106,10 @@ export class BitbybitGeometryRuntime {
|
||||
documentVersion: input.documentVersion,
|
||||
operationId: `${input.operationId}:native-stage:${index}`,
|
||||
operation,
|
||||
resultObjectId,
|
||||
...(index === input.transforms.length - 1 && input.resultObjectTag !== undefined ? { resultObjectTag: input.resultObjectTag } : {}),
|
||||
inputs: [{ inputId: `${stageId}:input:0`, objectId: previousObjectId, role: 'object', stageId: previousStageId, step: previousStep, ...(index === 0 && baseInput.objectTag !== undefined ? { objectTag: baseInput.objectTag } : {}), namingEvidence: previousNamingEvidence }],
|
||||
stages: [{ stageId, operation, inputIds: [`${stageId}:input:0`], ordinal: index }],
|
||||
objectStep: previousStep,
|
||||
...(step.type === 'linear' ? { direction: step.direction } : {}),
|
||||
...(step.type === 'polar' ? { axisOrigin: step.axisOrigin, direction: step.direction, angle: step.angle } : {}),
|
||||
@@ -2085,6 +2133,7 @@ export class BitbybitGeometryRuntime {
|
||||
previousStep = response.resultStep
|
||||
previousObjectId = resultObjectId
|
||||
previousStageId = stageId
|
||||
previousNamingEvidence = namingEvidenceForResponse(response, stageId, resultObjectId)
|
||||
}
|
||||
const records = captures[captures.length - 1].records as NativeTopologyHistoryRecords
|
||||
Object.defineProperty(records, 'stageCaptures', { value: captures, enumerable: false, configurable: false, writable: false })
|
||||
|
||||
@@ -81,7 +81,7 @@ export { NATIVE_OCCT_NAMING_ABI_UNAVAILABLE, nativeNamingAbiCapabilities, native
|
||||
export type { NativeNamingAbiCapabilities, NativeOcctHistoryCapabilities, NativeOcctHistoryExecution, NativeOcctHistoryInputTransport, NativeOcctHistoryProvider, NativeOcctHistoryProtocolResponse, NativeOcctHistoryRequest, NativeOcctHistoryStageTransport } from './nativeHistoryProtocol'
|
||||
export { captureFreeCadPrivateNamingEvidence, createFreeCadPrivateNamingAbiRequest, FREECAD_PRIVATE_NAMING_ABI_VERSION, FREECAD_PRIVATE_NAMING_MAX_REQUEST_BYTES, FREECAD_PRIVATE_NAMING_MAX_RESPONSE_BYTES, probeFreeCadPrivateNamingAbi } from './nativeNamingAbi'
|
||||
export type { FreeCadPrivateNamingAbiDescriptor, FreeCadPrivateNamingAbiProbe, FreeCadPrivateNamingAbiRequest, NativeFreeCadNamingAbiModule } from './nativeNamingAbi'
|
||||
export { assertNativeNamingEvidence, createFinalShapeOnlyNamingEvidence, createNativeStageNamingEvidence, hasNativeMappedNameEvidence, validateNativeNamingEvidence } from './nativeNamingEvidence'
|
||||
export { assertNativeNamingEvidence, cloneNativeNamingEvidence, createFinalShapeOnlyNamingEvidence, createNativeStageNamingEvidence, hasNativeMappedNameEvidence, validateNativeNamingEvidence } from './nativeNamingEvidence'
|
||||
export type { NativeMappedNameRef, NativeMappedNameRelation, NativeNamingEvidenceIssue, NativeNamingEvidenceReport, NativeNamingEvidenceStatus, NativeStageNamingEvidence } from './nativeNamingEvidence'
|
||||
export { NativeOcctHistoryWorkerProvider } from './nativeHistoryWorkerClient'
|
||||
export type { NativeOcctHistoryWorkerOptions } from './nativeHistoryWorkerClient'
|
||||
|
||||
@@ -390,7 +390,7 @@ const expressionReferences = (expression: string): string[] => {
|
||||
|
||||
const createDocumentFromTemplate = (template: DocumentTemplate): DocumentSnapshot => {
|
||||
const tree = template.tree.map((item) => ({ ...item, children: item.children ? [...item.children] : undefined }))
|
||||
const objects = tree.map(createObjectSnapshot)
|
||||
const objects = tree.map((item, index) => ({ ...createObjectSnapshot(item), nativeObjectTag: index + 1 }))
|
||||
const document: DocumentSnapshot = { id: template.id, label: template.label, version: template.version, dirty: template.dirty, readOnly: template.readOnly, units: template.units, tree, objects }
|
||||
document.dependencies = collectDependencyEdges(document)
|
||||
document.recompute = createRecomputeSnapshot(objects.map((object) => object.id))
|
||||
@@ -799,6 +799,7 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
|
||||
tree.push(item)
|
||||
}
|
||||
const objectSnapshot = createObjectSnapshot(item)
|
||||
objectSnapshot.nativeObjectTag = document.objects.reduce((maximum, object) => Math.max(maximum, object.nativeObjectTag ?? 0), 0) + 1
|
||||
if (commandId === 'create-body') {
|
||||
const tip = objectSnapshot.properties.find((property) => property.name === 'Tip')
|
||||
if (tip) tip.value = null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { NativeOcctHistoryOperation, NativeOcctHistoryResponse, NativeOcctHistoryStepProvider, NativeOcctMultiTransformStep } from './nativeHistoryProvider'
|
||||
import { captureFreeCadPrivateNamingEvidence, createFreeCadPrivateNamingAbiRequest, probeFreeCadPrivateNamingAbi } from './nativeNamingAbi'
|
||||
import type { NativeStageNamingEvidence } from './nativeNamingEvidence'
|
||||
|
||||
export const NATIVE_OCCT_HISTORY_PROTOCOL_VERSION = 1 as const
|
||||
|
||||
@@ -61,6 +62,8 @@ export type NativeOcctHistoryRequest = {
|
||||
documentId: string
|
||||
documentVersion: number
|
||||
operationId: string
|
||||
/** Document object produced by this native stage; distinct from the request operation ID. */
|
||||
resultObjectId?: string
|
||||
operation: NativeOcctHistoryOperation
|
||||
transformKind?: 'linear' | 'polar' | 'mirrored'
|
||||
transforms?: NativeOcctMultiTransformStep[]
|
||||
@@ -70,6 +73,7 @@ export type NativeOcctHistoryRequest = {
|
||||
inputs?: NativeOcctHistoryInputTransport[]
|
||||
stages?: NativeOcctHistoryStageTransport[]
|
||||
resultStepByStage?: Record<string, string>
|
||||
resultObjectTag?: number
|
||||
direction?: [number, number, number]
|
||||
axisOrigin?: [number, number, number]
|
||||
angle?: number
|
||||
@@ -87,9 +91,13 @@ export type NativeOcctHistoryRequest = {
|
||||
|
||||
export type NativeOcctHistoryInputTransport = {
|
||||
inputId: string
|
||||
/** Stable document object identity represented by this transport input. */
|
||||
objectId?: string
|
||||
role?: string
|
||||
stageId?: string
|
||||
step: string
|
||||
objectTag?: number
|
||||
namingEvidence?: NativeStageNamingEvidence
|
||||
}
|
||||
|
||||
export type NativeOcctHistoryStageTransport = {
|
||||
@@ -129,6 +137,7 @@ const booleanOperations: readonly NativeOcctHistoryOperation[] = ['fuse', 'cut',
|
||||
|
||||
const assertRequest = (request: NativeOcctHistoryRequest) => {
|
||||
if (request.protocolVersion !== NATIVE_OCCT_HISTORY_PROTOCOL_VERSION) throw new RangeError(`Unsupported native OCCT history protocol version: ${request.protocolVersion}.`)
|
||||
if (request.resultObjectId !== undefined && !request.resultObjectId.trim()) throw new TypeError('Native OCCT history resultObjectId must be non-empty when provided.')
|
||||
if (!request.requestId.trim() || !request.documentId.trim() || !request.operationId.trim()) throw new TypeError('Native OCCT history request IDs must be non-empty strings.')
|
||||
if (!Number.isSafeInteger(request.documentVersion) || request.documentVersion < 0) throw new RangeError('Native OCCT history documentVersion must be a non-negative integer.')
|
||||
if (!['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'].includes(request.operation)) throw new RangeError(`Unsupported native OCCT history operation: ${String(request.operation)}.`)
|
||||
@@ -162,6 +171,11 @@ const assertRequest = (request: NativeOcctHistoryRequest) => {
|
||||
if (!step.startsWith('ISO-10303-21;')) throw new TypeError(`Native OCCT result step for '${stageId}' requires STEP text.`)
|
||||
}
|
||||
}
|
||||
if (request.resultObjectTag !== undefined && (!Number.isSafeInteger(request.resultObjectTag) || request.resultObjectTag <= 0)) throw new RangeError('Native OCCT history resultObjectTag must be a positive safe integer.')
|
||||
for (const input of request.inputs ?? []) {
|
||||
if (input.objectId !== undefined && !input.objectId.trim()) throw new TypeError(`Native OCCT history input ${input.inputId} objectId must be non-empty when provided.`)
|
||||
if (input.objectTag !== undefined && (!Number.isSafeInteger(input.objectTag) || input.objectTag <= 0)) throw new RangeError(`Native OCCT history input ${input.inputId} objectTag must be a positive safe integer.`)
|
||||
}
|
||||
if (booleanOperations.includes(request.operation)) {
|
||||
if (!request.toolStep?.startsWith('ISO-10303-21;')) throw new TypeError('Native Boolean history transport requires object and tool STEP text.')
|
||||
} else if (request.operation === 'pocket') {
|
||||
@@ -359,8 +373,9 @@ export class DirectNativeOcctHistoryProvider implements NativeOcctHistoryProvide
|
||||
operationId: request.operationId,
|
||||
operation: request.operation,
|
||||
stageId,
|
||||
resultObjectId: request.operationId,
|
||||
inputs: (request.inputs ?? [{ inputId: 'object', role: 'object', step: request.objectStep }, ...(request.toolStep ? [{ inputId: 'tool', role: 'tool', step: request.toolStep }] : [])]).map(({ inputId, role, stageId: inputStageId, step }) => ({ inputId, step, ...(role ? { role } : {}), ...(inputStageId ? { stageId: inputStageId } : {}) })),
|
||||
resultObjectId: request.resultObjectId ?? request.operationId,
|
||||
...(request.resultObjectTag === undefined ? {} : { resultObjectTag: request.resultObjectTag }),
|
||||
inputs: (request.inputs ?? [{ inputId: 'object', role: 'object', step: request.objectStep }, ...(request.toolStep ? [{ inputId: 'tool', role: 'tool', step: request.toolStep }] : [])]).map(({ inputId, objectId, role, stageId: inputStageId, step, objectTag, namingEvidence }) => ({ inputId, step, ...(objectId ? { objectId } : {}), ...(role ? { role } : {}), ...(inputStageId ? { stageId: inputStageId } : {}), ...(objectTag === undefined ? {} : { objectTag }), ...(namingEvidence ? { namingEvidence } : {}) })),
|
||||
stages: orderedStages.map((stage) => ({ ...stage, inputIds: [...stage.inputIds] })),
|
||||
...(stagedHistory.resultStep ? { resultStep: stagedHistory.resultStep } : {}),
|
||||
...(stagedHistory.resultBrep ? { resultBrep: stagedHistory.resultBrep } : {}),
|
||||
|
||||
@@ -94,8 +94,9 @@ scope.onmessage = ({ data }: MessageEvent<WorkerRequest>) => {
|
||||
operationId: request.operationId,
|
||||
operation: request.operation,
|
||||
stageId,
|
||||
resultObjectId: request.operationId,
|
||||
inputs: (request.inputs ?? [{ inputId: 'object', role: 'object', step: request.objectStep }, ...(request.toolStep ? [{ inputId: 'tool', role: 'tool', step: request.toolStep }] : [])]).map(({ inputId, role, stageId: inputStageId, step }) => ({ inputId, step, ...(role ? { role } : {}), ...(inputStageId ? { stageId: inputStageId } : {}) })),
|
||||
resultObjectId: request.resultObjectId ?? request.operationId,
|
||||
...(request.resultObjectTag === undefined ? {} : { resultObjectTag: request.resultObjectTag }),
|
||||
inputs: (request.inputs ?? [{ inputId: 'object', role: 'object', step: request.objectStep }, ...(request.toolStep ? [{ inputId: 'tool', role: 'tool', step: request.toolStep }] : [])]).map(({ inputId, objectId, role, stageId: inputStageId, step, objectTag, namingEvidence }) => ({ inputId, step, ...(objectId ? { objectId } : {}), ...(role ? { role } : {}), ...(inputStageId ? { stageId: inputStageId } : {}), ...(objectTag === undefined ? {} : { objectTag }), ...(namingEvidence ? { namingEvidence } : {}) })),
|
||||
stages: orderedStages.map((stage) => ({ ...stage, inputIds: [...stage.inputIds] })),
|
||||
...(history.resultStep ? { resultStep: history.resultStep } : {}),
|
||||
...(history.resultBrep ? { resultBrep: history.resultBrep } : {}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { migrateElementMap2Schema, validateElementMap2 } from './elementMap2'
|
||||
import { assertNativeNamingEvidence, type NativeStageNamingEvidence } from './nativeNamingEvidence'
|
||||
import { assertNativeNamingEvidence, cloneNativeNamingEvidence, type NativeStageNamingEvidence } from './nativeNamingEvidence'
|
||||
import { migrateStringHasherSchema, validateElementMap2StringHasherEvidence, validateStringHasherTable } from './stringHasher'
|
||||
import type { NativeOcctHistoryOperation, NativeOcctHistoryResponse } from './nativeHistoryProvider'
|
||||
|
||||
@@ -42,7 +42,8 @@ export type FreeCadPrivateNamingAbiRequest = {
|
||||
operation: NativeOcctHistoryOperation
|
||||
stageId: string
|
||||
resultObjectId: string
|
||||
inputs: Array<{ inputId: string; role?: string; stageId?: string; step: string }>
|
||||
resultObjectTag?: number
|
||||
inputs: Array<{ inputId: string; objectId?: string; role?: string; stageId?: string; step: string; objectTag?: number; namingEvidence?: NativeStageNamingEvidence }>
|
||||
stages: Array<{ stageId: string; operation?: NativeOcctHistoryOperation; inputIds: string[]; resultStageId?: string; ordinal: number }>
|
||||
resultStep?: string
|
||||
resultBrep?: string
|
||||
@@ -91,12 +92,15 @@ export const probeFreeCadPrivateNamingAbi = (module: NativeFreeCadNamingAbiModul
|
||||
export const createFreeCadPrivateNamingAbiRequest = (input: Omit<FreeCadPrivateNamingAbiRequest, 'schemaVersion'>): FreeCadPrivateNamingAbiRequest => {
|
||||
if (!input.requestId.trim() || !input.documentId.trim() || !input.operationId.trim() || !input.stageId.trim() || !input.resultObjectId.trim()) throw new TypeError('FreeCAD naming ABI request IDs must be non-empty.')
|
||||
if (!Number.isSafeInteger(input.documentVersion) || input.documentVersion < 0) throw new RangeError('FreeCAD naming ABI documentVersion must be a non-negative integer.')
|
||||
if (input.resultObjectTag !== undefined && (!Number.isSafeInteger(input.resultObjectTag) || input.resultObjectTag <= 0)) throw new RangeError('FreeCAD naming ABI resultObjectTag must be a positive safe integer.')
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
...input,
|
||||
inputs: input.inputs.map((entry) => {
|
||||
if (!entry.inputId.trim() || !entry.step.trim()) throw new TypeError('FreeCAD naming ABI inputs require non-empty inputId and STEP text.')
|
||||
return { ...entry }
|
||||
if (entry.objectId !== undefined && !entry.objectId.trim()) throw new TypeError(`FreeCAD naming ABI input ${entry.inputId} objectId must be non-empty when provided.`)
|
||||
if (entry.objectTag !== undefined && (!Number.isSafeInteger(entry.objectTag) || entry.objectTag <= 0)) throw new RangeError(`FreeCAD naming ABI input ${entry.inputId} objectTag must be a positive safe integer.`)
|
||||
return { ...entry, ...(entry.namingEvidence ? { namingEvidence: cloneNativeNamingEvidence(assertNativeNamingEvidence(entry.namingEvidence)) } : {}) }
|
||||
}),
|
||||
stages: input.stages.map((entry) => ({ ...entry, inputIds: [...entry.inputIds] })),
|
||||
...(input.resultStepByStage ? { resultStepByStage: { ...input.resultStepByStage } } : {}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AnyElementMap2Document, ElementMap2MappedNameReference } from './elementMap2'
|
||||
import { migrateStringHasherSchema, validateStringHasherTable, type AnyStringHasherTable } from './stringHasher'
|
||||
import { cloneElementMap2, migrateElementMap2Schema, type AnyElementMap2Document, type ElementMap2MappedNameReference } from './elementMap2'
|
||||
import { cloneStringHasherTable, migrateStringHasherSchema, validateStringHasherTable, type AnyStringHasherTable } from './stringHasher'
|
||||
|
||||
export type NativeMappedNameRelation = 'preserved' | 'modified' | 'generated' | 'deleted' | 'ambiguous'
|
||||
|
||||
@@ -119,6 +119,22 @@ export const assertNativeNamingEvidence = <T extends NativeStageNamingEvidence>(
|
||||
return evidence
|
||||
}
|
||||
|
||||
export const cloneNativeNamingEvidence = (evidence: NativeStageNamingEvidence): NativeStageNamingEvidence => ({
|
||||
...evidence,
|
||||
mappedNames: evidence.mappedNames?.map((mapped) => ({
|
||||
...mapped,
|
||||
reference: {
|
||||
...mapped.reference,
|
||||
indexedName: mapped.reference.indexedName ? { ...mapped.reference.indexedName } : undefined,
|
||||
stringIds: mapped.reference.stringIds ? [...mapped.reference.stringIds] : undefined,
|
||||
},
|
||||
sourceRefs: mapped.sourceRefs?.map((source) => ({ ...source })),
|
||||
candidates: mapped.candidates?.map((candidate) => ({ ...candidate })),
|
||||
})),
|
||||
stringHasher: evidence.stringHasher ? cloneStringHasherTable(evidence.stringHasher) : undefined,
|
||||
elementMap2: evidence.elementMap2 ? cloneElementMap2(migrateElementMap2Schema(evidence.elementMap2)) : undefined,
|
||||
})
|
||||
|
||||
export const createFinalShapeOnlyNamingEvidence = (stageId: string, resultObjectId: string, reason = 'Native provider returned final Shape without MappedNameRef/StringHasher evidence.'): NativeStageNamingEvidence => ({
|
||||
schemaVersion: 1,
|
||||
stageId,
|
||||
|
||||
@@ -90,6 +90,13 @@ const initialize = async (requestedPath?: string, migrationFailureVersion?: numb
|
||||
|
||||
const saveDocument = (document: DocumentSnapshot) => {
|
||||
if (!database) throw new Error('Persistence database is not initialized.')
|
||||
const nativeObjectTags = new Set<number>()
|
||||
for (const object of document.objects) {
|
||||
if (object.nativeObjectTag === undefined) continue
|
||||
if (!Number.isSafeInteger(object.nativeObjectTag) || object.nativeObjectTag <= 0) throw new RangeError(`Object ${object.id} nativeObjectTag must be a positive safe integer.`)
|
||||
if (nativeObjectTags.has(object.nativeObjectTag)) throw new RangeError(`nativeObjectTag ${object.nativeObjectTag} is assigned to more than one object.`)
|
||||
nativeObjectTags.add(object.nativeObjectTag)
|
||||
}
|
||||
const now = Date.now()
|
||||
database.exec('BEGIN;')
|
||||
try {
|
||||
@@ -98,7 +105,7 @@ const saveDocument = (document: DocumentSnapshot) => {
|
||||
database.exec({ sql: 'DELETE FROM objects WHERE document_id = ?', bind: [document.id] })
|
||||
const parentByChild = new Map<string, string>()
|
||||
for (const item of document.tree) for (const childId of item.children || []) parentByChild.set(childId, item.id)
|
||||
document.tree.forEach((item, ordinal) => { const object = document.objects.find((candidate) => candidate.id === item.id); database?.exec({ sql: 'INSERT INTO objects(id, document_id, parent_id, label, object_type, state, detail, children_json, ordinal, sketch_json, topology_json) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', bind: [item.id, document.id, parentByChild.get(item.id) || null, item.label, item.type, item.state || null, item.detail || null, JSON.stringify(item.children || []), ordinal, object?.sketch ? JSON.stringify(object.sketch) : null, object?.topology ? JSON.stringify(object.topology) : null] }) })
|
||||
document.tree.forEach((item, ordinal) => { const object = document.objects.find((candidate) => candidate.id === item.id); database?.exec({ sql: 'INSERT INTO objects(id, document_id, parent_id, label, object_type, state, detail, children_json, ordinal, sketch_json, topology_json, native_object_tag) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', bind: [item.id, document.id, parentByChild.get(item.id) || null, item.label, item.type, item.state || null, item.detail || null, JSON.stringify(item.children || []), ordinal, object?.sketch ? JSON.stringify(object.sketch) : null, object?.topology ? JSON.stringify(object.topology) : null, object?.nativeObjectTag ?? null] }) })
|
||||
for (const object of document.objects) for (const property of object.properties) database.exec({ sql: 'INSERT INTO object_properties(document_id, object_id, name, value_json, property_type, updated_at) VALUES(?, ?, ?, ?, ?, ?)', bind: [document.id, object.id, property.name, JSON.stringify(property), property.type, now] })
|
||||
database.exec({ sql: 'DELETE FROM dependencies WHERE document_id = ?', bind: [document.id] })
|
||||
for (const edge of document.dependencies ?? []) database.exec({ sql: 'INSERT INTO dependencies(document_id, source_id, target_id, relation, property_name, reference) VALUES(?, ?, ?, ?, ?, ?)', bind: [document.id, edge.sourceId, edge.targetId, edge.relation, edge.propertyName ?? null, edge.reference ?? null] })
|
||||
@@ -132,7 +139,7 @@ const loadDocument = (documentId: string): DocumentSnapshot | null => {
|
||||
const documents = database.exec({ sql: 'SELECT id, label, version, dirty, read_only, units, recompute_json FROM documents WHERE id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
|
||||
const row = documents[0]
|
||||
if (!row) return null
|
||||
const objects = database.exec({ sql: 'SELECT id, label, object_type, state, detail, children_json, sketch_json, topology_json FROM objects WHERE document_id = ? ORDER BY ordinal', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
|
||||
const objects = database.exec({ sql: 'SELECT id, label, object_type, state, detail, children_json, sketch_json, topology_json, native_object_tag FROM objects WHERE document_id = ? ORDER BY ordinal', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | number | null>>
|
||||
const propertyRows = database.exec({ sql: 'SELECT object_id, value_json FROM object_properties WHERE document_id = ? ORDER BY object_id, name', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string>>
|
||||
const dependencyRows = database.exec({ sql: 'SELECT source_id, target_id, relation, property_name, reference FROM dependencies WHERE document_id = ?', bind: [documentId], rowMode: 'object', returnValue: 'resultRows' }) as Array<Record<string, string | null>>
|
||||
const propertiesByObject = new Map<string, ObjectPropertySnapshot[]>()
|
||||
@@ -142,11 +149,29 @@ const loadDocument = (documentId: string): DocumentSnapshot | null => {
|
||||
propertiesByObject.set(String(propertyRow.object_id), properties)
|
||||
}
|
||||
const tree: ModelTreeItem[] = objects.map((object) => ({ id: String(object.id), label: String(object.label), type: String(object.object_type) as ModelTreeItem['type'], state: object.state ? String(object.state) as ModelTreeItem['state'] : undefined, detail: object.detail ? String(object.detail) : undefined, children: object.children_json ? JSON.parse(String(object.children_json)) as string[] : undefined }))
|
||||
const usedNativeObjectTags = new Set<number>()
|
||||
const persistedNativeObjectTags = new Map<string, number>()
|
||||
for (const object of objects) {
|
||||
if (object.native_object_tag === null || object.native_object_tag === undefined) continue
|
||||
const nativeObjectTag = Number(object.native_object_tag)
|
||||
if (!Number.isSafeInteger(nativeObjectTag) || nativeObjectTag <= 0) throw new Error(`Persisted object ${String(object.id)} has an invalid native object tag.`)
|
||||
if (usedNativeObjectTags.has(nativeObjectTag)) throw new Error(`Persisted native object tag ${nativeObjectTag} is duplicated.`)
|
||||
usedNativeObjectTags.add(nativeObjectTag)
|
||||
persistedNativeObjectTags.set(String(object.id), nativeObjectTag)
|
||||
}
|
||||
let nextNativeObjectTag = 1
|
||||
const objectSnapshots: DocumentObjectSnapshot[] = tree.map((item) => {
|
||||
const properties = propertiesByObject.get(item.id) ?? []
|
||||
const typeId = properties.find((property) => property.name === 'TypeId')?.value
|
||||
const row = objects.find((candidate) => String(candidate.id) === item.id)
|
||||
return { id: item.id, typeId: typeof typeId === 'string' ? typeId : item.type, properties, sketch: row?.sketch_json ? JSON.parse(String(row.sketch_json)) : undefined, topology: row?.topology_json ? JSON.parse(String(row.topology_json)) : undefined }
|
||||
let nativeObjectTag = persistedNativeObjectTags.get(item.id)
|
||||
if (nativeObjectTag === undefined) {
|
||||
while (usedNativeObjectTags.has(nextNativeObjectTag)) nextNativeObjectTag += 1
|
||||
nativeObjectTag = nextNativeObjectTag
|
||||
usedNativeObjectTags.add(nativeObjectTag)
|
||||
nextNativeObjectTag += 1
|
||||
}
|
||||
return { id: item.id, typeId: typeof typeId === 'string' ? typeId : item.type, nativeObjectTag, properties, sketch: row?.sketch_json ? JSON.parse(String(row.sketch_json)) : undefined, topology: row?.topology_json ? JSON.parse(String(row.topology_json)) : undefined }
|
||||
})
|
||||
return {
|
||||
id: String(row.id),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const PROJECT_SCHEMA_VERSION = 6
|
||||
export const PROJECT_SCHEMA_VERSION = 7
|
||||
|
||||
export type ProjectSchemaMigration = { version: number; sql: string }
|
||||
|
||||
@@ -141,4 +141,5 @@ export const PROJECT_SCHEMA_MIGRATIONS = [
|
||||
{ version: 4, sql: 'ALTER TABLE objects ADD COLUMN sketch_json TEXT;' },
|
||||
{ version: 5, sql: 'CREATE TABLE IF NOT EXISTS document_checkpoints (document_id TEXT NOT NULL, version INTEGER NOT NULL, snapshot_json TEXT NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY (document_id, version), FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE); CREATE INDEX IF NOT EXISTS checkpoints_document_created ON document_checkpoints(document_id, created_at DESC);' },
|
||||
{ version: 6, sql: 'ALTER TABLE objects ADD COLUMN topology_json TEXT;' },
|
||||
{ version: 7, sql: 'ALTER TABLE objects ADD COLUMN native_object_tag INTEGER;' },
|
||||
] as const
|
||||
|
||||
@@ -580,8 +580,16 @@ const topologyForObject = async (
|
||||
documentVersion: context.documentVersion,
|
||||
operationId,
|
||||
operation: nativeOperation,
|
||||
inputs: orderedNativeInputs.map(({ source, shape: inputShape }, index) => ({ objectId: source.id, shape: inputShape, inputId: `${object.id}:input:${index}`, role: index === 0 ? 'object' : 'tool' })),
|
||||
inputs: orderedNativeInputs.map(({ source, shape: inputShape }, index) => ({
|
||||
objectId: source.id,
|
||||
shape: inputShape,
|
||||
inputId: `${object.id}:input:${index}`,
|
||||
role: index === 0 ? 'object' : 'tool',
|
||||
...(source.nativeObjectTag === undefined ? {} : { objectTag: source.nativeObjectTag }),
|
||||
namingEvidence: source.topology?.history?.namingEvidence?.at(-1),
|
||||
})),
|
||||
stages: [{ stageId: `${object.id}:stage:0`, operation: nativeOperation, inputObjectIds: orderedNativeInputs.map(({ source }) => source.id), resultObjectId: object.id, ordinal: 0 }],
|
||||
...(object.nativeObjectTag === undefined ? {} : { resultObjectTag: object.nativeObjectTag }),
|
||||
...(nativeOperation === 'fillet' ? { radius: numericProperty('Radius', 1) } : {}),
|
||||
...(nativeOperation === 'chamfer' ? { distance: numericProperty('Size', numericProperty('Distance', 1)) } : {}),
|
||||
...(nativeOperation === 'hole' ? {
|
||||
|
||||
@@ -108,6 +108,8 @@ export type ObjectPropertySnapshot = {
|
||||
export type DocumentObjectSnapshot = {
|
||||
id: string
|
||||
typeId: string
|
||||
/** Stable positive App::DocumentObject tag persisted by FreeCAD's Document.xml. */
|
||||
nativeObjectTag?: number
|
||||
properties: ObjectPropertySnapshot[]
|
||||
sketch?: SketchSnapshot
|
||||
topology?: ObjectTopologySnapshot
|
||||
@@ -407,8 +409,10 @@ export type NativeTopologyHistoryInput = GeometryDocumentContext & {
|
||||
ruled?: boolean
|
||||
offset?: number
|
||||
joinType?: 'Arc' | 'Intersection'
|
||||
inputs: Array<{ objectId: string; shape: ShapeHandle; inputId?: string; role?: string; stageId?: string }>
|
||||
inputs: Array<{ objectId: string; shape: ShapeHandle; inputId?: string; role?: string; stageId?: string; objectTag?: number; namingEvidence?: NativeStageNamingEvidence }>
|
||||
stages?: NativeTopologyHistoryStage[]
|
||||
/** Stable FreeCAD owner tag used when encoding the result ElementMap. */
|
||||
resultObjectTag?: number
|
||||
result: ShapeHandle
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user