P5: add facade sketch model solver and persistence
This commit is contained in:
@@ -6,6 +6,7 @@ import { PersistenceWriteQueue, ProjectAutosaveScheduler } from '../src/facade/p
|
||||
import { DependencyGraph } from '../src/facade/dependencyGraph'
|
||||
import { evaluateQuantityExpression, quantityFromNumber } from '../src/facade/units'
|
||||
import { createSubshapeRefs, matchSubshapes, signatureForFace } from '../src/facade/topologyNaming'
|
||||
import { createSketch, solveSketch } from '../src/facade/sketcher'
|
||||
import { assertShapeHandleIntegrity, normalizeBitbybitMesh, validateBooleanUnionInput, validateBoxInput, validateConeInput, validateCylinderInput, validatePadInput, validatePlacementInput, validatePlanarProfile, validateRevolutionInput, validateSphereInput } from '../src/facade/geometryRuntime'
|
||||
import type { ShapeHandle } from '../src/facade/types'
|
||||
|
||||
@@ -74,8 +75,8 @@ test('Bitbybit face meshes are normalized into a facade-owned indexed asset', ()
|
||||
})
|
||||
|
||||
test('project schema is versioned and covers the FreeCAD document graph', () => {
|
||||
assert.equal(PROJECT_SCHEMA_VERSION, 3)
|
||||
assert.deepEqual(PROJECT_SCHEMA_MIGRATIONS.map((migration) => migration.version), [1, 2, 3])
|
||||
assert.equal(PROJECT_SCHEMA_VERSION, 4)
|
||||
assert.deepEqual(PROJECT_SCHEMA_MIGRATIONS.map((migration) => migration.version), [1, 2, 3, 4])
|
||||
for (const table of ['projects', 'documents', 'objects', 'object_properties', 'dependencies', 'transactions', 'resources']) assert.match(PROJECT_SCHEMA_SQL, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}`))
|
||||
assert.match(PROJECT_SCHEMA_SQL, /CREATE INDEX IF NOT EXISTS objects_document_ordinal/)
|
||||
})
|
||||
@@ -110,6 +111,40 @@ test('topology signatures are independent of transient face indexes and flag amb
|
||||
assert.equal(matches[0].previousId, first.refs[0].persistentId)
|
||||
})
|
||||
|
||||
test('basic sketch solver applies geometric constraints and reports remaining degrees of freedom', () => {
|
||||
const sketch = createSketch('sketch-test', [{ id: 'line-1', type: 'line', start: { x: 0, y: 0 }, end: { x: 1, y: 0 } }], [
|
||||
{ id: 'horizontal-1', type: 'horizontal', geometryId: 'line-1' },
|
||||
{ id: 'length-1', type: 'distance', first: { geometryId: 'line-1', point: 'start' }, second: { geometryId: 'line-1', point: 'end' }, value: 5 },
|
||||
])
|
||||
const result = solveSketch(sketch)
|
||||
assert.equal(result.status, 'under-constrained')
|
||||
assert.ok(result.residual <= 1e-7)
|
||||
assert.equal(result.snapshot.geometry[0].type, 'line')
|
||||
assert.equal((result.snapshot.geometry[0] as Extract<typeof result.snapshot.geometry[number], { type: 'line' }>).end.x, 5)
|
||||
|
||||
const conflict = solveSketch(createSketch('conflict', [{ id: 'line-1', type: 'line', start: { x: 0, y: 0 }, end: { x: 1, y: 1 } }], [
|
||||
{ id: 'block-1', type: 'block', geometryId: 'line-1' },
|
||||
{ id: 'horizontal-1', type: 'horizontal', geometryId: 'line-1' },
|
||||
]))
|
||||
assert.equal(conflict.status, 'conflicting')
|
||||
assert.ok(conflict.diagnostics.some((diagnostic) => diagnostic.code === 'SOLVER_NOT_CONVERGED'))
|
||||
})
|
||||
|
||||
test('sketcher operations are facade transactions and survive the project fallback store', async () => {
|
||||
const facade = createMockFacade()
|
||||
const before = facade.app.document.getActive().version
|
||||
const added = facade.app.sketcher.addGeometry('sketch', { id: 'line-1', type: 'line', start: { x: 0, y: 0 }, end: { x: 1, y: 0 } })
|
||||
assert.equal(added.geometry.length, 1)
|
||||
facade.app.sketcher.addConstraint('sketch', { id: 'horizontal-1', type: 'horizontal', geometryId: 'line-1' })
|
||||
const solved = facade.app.sketcher.solve('sketch')
|
||||
assert.equal(solved.status, 'under-constrained')
|
||||
assert.ok(facade.app.document.getActive().version >= before + 3)
|
||||
assert.match(String(facade.app.document.getObject('sketch')?.properties.find((property) => property.name === 'ConstraintStatus')?.value), /Under-constrained/)
|
||||
await facade.project.save()
|
||||
const loaded = await facade.project.load('doc-pump-housing')
|
||||
assert.equal(loaded?.objects.find((object) => object.id === 'sketch')?.sketch?.constraints[0].id, 'horizontal-1')
|
||||
})
|
||||
|
||||
test('dependency graph propagates dirty state and orders dependencies', () => {
|
||||
const graph = new DependencyGraph([
|
||||
{ sourceId: 'pocket', targetId: 'pad', relation: 'link' },
|
||||
|
||||
Reference in New Issue
Block a user