feat: persist stable sketch external geometry
This commit is contained in:
@@ -27,7 +27,7 @@ import { BitbybitGeometryRuntime } from './geometryRuntime'
|
||||
import { ThreeViewportAdapter } from './threeViewport'
|
||||
import { DependencyGraph, createRecomputeSnapshot, type DependencyEdge } from './dependencyGraph'
|
||||
import { convertQuantity, evaluateQuantityExpression, getUnit, quantityDimensionForUnit, quantityFromNumber, quantityFromUnit, type Quantity } from './units'
|
||||
import { cloneSketch, createSketch, solveSketch, type SketchConstraint, type SketchGeometry, type SketchSnapshot } from './sketcher'
|
||||
import { cloneSketch, cloneSketchConstraint, cloneSketchGeometry, createSketch, solveSketch, type SketchConstraint, type SketchExternalGeometry, type SketchGeometry, type SketchSnapshot } from './sketcher'
|
||||
import { createFacadeGeometryRecomputeExecutor, RecomputeCoordinator, type RecomputeExecutionOptions } from './recomputeEngine'
|
||||
import { inspectFcstdArchive } from './fcstd'
|
||||
import { buildDiagnosticTree, buildRecomputeDiagnostics, cloneDiagnostic, replaceRecomputeDiagnostics } from './diagnostics'
|
||||
@@ -168,6 +168,9 @@ const collectDependencyEdges = (document: Pick<DocumentSnapshot, 'objects'>): De
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const external of object.sketch?.externalGeometry ?? []) {
|
||||
if (objectIds.has(external.source.objectId)) edges.push({ sourceId: object.id, targetId: external.source.objectId, relation: 'topo-ref', propertyName: `ExternalGeometry:${external.id}`, reference: external.source.persistentId })
|
||||
}
|
||||
}
|
||||
return edges
|
||||
}
|
||||
@@ -572,6 +575,7 @@ export function createMockFacade(): BitBybitWebCadFacade {
|
||||
document.objects[objectIndex].sketch = solved.snapshot
|
||||
const status = document.objects[objectIndex].properties.find((property) => property.name === 'ConstraintStatus')
|
||||
if (status) status.value = solved.status === 'solved' ? 'Fully constrained' : solved.status === 'under-constrained' ? `Under-constrained (${solved.degreesOfFreedom} DOF)` : solved.status === 'conflicting' ? 'Conflicting constraints' : 'Invalid constraints'
|
||||
document.dependencies = collectDependencyEdges(document)
|
||||
markDocumentTouched(document, [objectId])
|
||||
document.version += 1
|
||||
document.dirty = true
|
||||
@@ -580,11 +584,18 @@ export function createMockFacade(): BitBybitWebCadFacade {
|
||||
}
|
||||
const addSketchGeometry = (objectId: string, geometry: SketchGeometry) => updateSketch(objectId, (sketch) => {
|
||||
if (sketch.geometry.some((candidate) => candidate.id === geometry.id)) throw new RangeError(`Sketch geometry already exists: ${geometry.id}`)
|
||||
sketch.geometry.push({ ...geometry } as SketchGeometry)
|
||||
sketch.geometry.push(cloneSketchGeometry(geometry))
|
||||
})
|
||||
const addSketchExternalGeometry = (objectId: string, external: SketchExternalGeometry) => updateSketch(objectId, (sketch) => {
|
||||
if (!external.id.trim()) throw new RangeError('External geometry id is required.')
|
||||
if (sketch.externalGeometry.some((candidate) => candidate.id === external.id)) throw new RangeError(`External geometry already exists: ${external.id}`)
|
||||
if (external.source.objectId === objectId) throw new RangeError('A sketch cannot import external geometry from itself.')
|
||||
if (external.source.status === 'deleted') throw new RangeError('Deleted topology cannot be imported as external geometry.')
|
||||
sketch.externalGeometry.push({ ...external, source: { ...external.source, candidates: external.source.candidates ? [...external.source.candidates] : undefined }, projection: cloneSketchGeometry(external.projection), construction: true })
|
||||
})
|
||||
const addSketchConstraint = (objectId: string, constraint: SketchConstraint) => updateSketch(objectId, (sketch) => {
|
||||
if (sketch.constraints.some((candidate) => candidate.id === constraint.id)) throw new RangeError(`Sketch constraint already exists: ${constraint.id}`)
|
||||
sketch.constraints.push({ ...constraint } as SketchConstraint)
|
||||
sketch.constraints.push(cloneSketchConstraint(constraint))
|
||||
})
|
||||
const solveSketchObject = (objectId: string) => {
|
||||
const objectIndex = state.document.objects.findIndex((object) => object.id === objectId)
|
||||
@@ -664,7 +675,7 @@ export function createMockFacade(): BitBybitWebCadFacade {
|
||||
}
|
||||
|
||||
const facade: BitBybitWebCadFacade = {
|
||||
app: { document: { getActive: () => getState().document, getObject: (objectId) => { const object = state.document.objects.find((candidate) => candidate.id === objectId); return object ? { ...object, properties: object.properties.map((property) => ({ ...property, options: property.options ? [...property.options] : undefined })), sketch: object.sketch ? cloneSketch(object.sketch) : undefined } : null }, create: (label) => { recomputeCoordinator.cancel(); clearFeatureShapes(); commit({ ...state, document: createDocument(label), selectedObjectId: '' }); return getState().document }, load: loadDocument, markDirty: () => { commit({ ...state, document: { ...state.document, dirty: true } }) }, setProperty, setExpression, recompute: recomputeDocument, recomputeAsync: recomputeDocumentAsync, cancelRecompute: () => recomputeCoordinator.cancel(), getDependencies: () => (state.document.dependencies ?? []).map((edge) => ({ ...edge })) }, expression: { evaluate: (expression, variables = {}) => evaluateQuantityExpression(expression, new Map(Object.entries(variables))), dimensionForUnit: quantityDimensionForUnit }, sketcher: { get: getSketch, addGeometry: addSketchGeometry, addConstraint: addSketchConstraint, solve: solveSketchObject } },
|
||||
app: { document: { getActive: () => getState().document, getObject: (objectId) => { const object = state.document.objects.find((candidate) => candidate.id === objectId); return object ? { ...object, properties: object.properties.map((property) => ({ ...property, options: property.options ? [...property.options] : undefined })), sketch: object.sketch ? cloneSketch(object.sketch) : undefined } : null }, create: (label) => { recomputeCoordinator.cancel(); clearFeatureShapes(); commit({ ...state, document: createDocument(label), selectedObjectId: '' }); return getState().document }, load: loadDocument, markDirty: () => { commit({ ...state, document: { ...state.document, dirty: true } }) }, setProperty, setExpression, recompute: recomputeDocument, recomputeAsync: recomputeDocumentAsync, cancelRecompute: () => recomputeCoordinator.cancel(), getDependencies: () => (state.document.dependencies ?? []).map((edge) => ({ ...edge })) }, expression: { evaluate: (expression, variables = {}) => evaluateQuantityExpression(expression, new Map(Object.entries(variables))), dimensionForUnit: quantityDimensionForUnit }, sketcher: { get: getSketch, addGeometry: addSketchGeometry, addExternalGeometry: addSketchExternalGeometry, addConstraint: addSketchConstraint, solve: solveSketchObject } },
|
||||
history: { canUndo: () => undoStack.length > 0, canRedo: () => redoStack.length > 0, undo: () => { const previous = undoStack.pop(); if (!previous) return; clearFeatureShapes(); redoStack.push(getState()); state = previous; emitState(); notify('Undo applied') }, redo: () => { const next = redoStack.pop(); if (!next) return; clearFeatureShapes(); undoStack.push(getState()); state = next; emitState(); notify('Redo applied') } },
|
||||
gui: { workbench: { list: () => Object.keys(workbenchDefinitions) as WorkbenchId[], getActive: () => state.activeWorkbench, setActive }, command: { getState: (commandId) => commandState(commandId, state.activeWorkbench, state.selectedObjectId, state.document.objects.find((object) => object.id === state.selectedObjectId)?.typeId), list: (workbench) => workbenchDefinitions[workbench].groups.flatMap((group) => group.commands), execute } },
|
||||
selection: { getObjectId: () => state.selectedObjectId, select, clear: () => select('') },
|
||||
|
||||
Reference in New Issue
Block a user