From 589b2eb0f6202b7fafe2b8d4203b622e79dfbd1f Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 2 Aug 2026 19:31:25 -0400 Subject: [PATCH] feat: add Part shape diagnostics --- config/compatibility-matrix.json | 2 +- src/facade/mockFacade.ts | 21 ++++++++++++++++++--- tests/facade.test.ts | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/config/compatibility-matrix.json b/config/compatibility-matrix.json index fd6a6e7..1aa02fc 100644 --- a/config/compatibility-matrix.json +++ b/config/compatibility-matrix.json @@ -17,7 +17,7 @@ "Inspection": { "status": "ui-manifest", "level": "unsupported", "commands": ["measure-distance", "measure-angle", "measure-area", "section", "check-dependencies"] } }, "facadeCapabilities": { - "geometry": { "level": "experimental", "provider": "BitBybit OCCT 1.1.1", "operations": ["box", "cylinder", "sphere", "cone", "placement", "union", "cut", "intersection", "part-primitive-recompute", "part-boolean-recompute", "pad", "pocket", "pocket-through-all", "revolution", "fillet", "chamfer", "feature-shape-cache", "face-edge-vertex-topology", "step-export", "stl-export"] }, + "geometry": { "level": "experimental", "provider": "BitBybit OCCT 1.1.1", "operations": ["box", "cylinder", "sphere", "cone", "placement", "union", "cut", "intersection", "part-primitive-recompute", "part-boolean-recompute", "shape-check", "pad", "pocket", "pocket-through-all", "revolution", "fillet", "chamfer", "feature-shape-cache", "face-edge-vertex-topology", "step-export", "stl-export"] }, "document": { "level": "experimental", "operations": ["typed-properties", "expressions", "units", "dependency-dag", "parallel-level-recompute", "sync-recompute", "async-generation-recompute", "undo-redo"] }, "sketcher": { "level": "experimental", "operations": ["point-line-circle-arc-model", "basic-constraints", "diameter-symmetric-tangent", "basic-solver", "persistence"] }, "fcstd": { "level": "read-only", "operations": ["zip-preflight", "document-xml-metadata", "proxy-report", "script-isolation"] } diff --git a/src/facade/mockFacade.ts b/src/facade/mockFacade.ts index 0bf2440..9f2e208 100644 --- a/src/facade/mockFacade.ts +++ b/src/facade/mockFacade.ts @@ -163,11 +163,11 @@ const createDocument = (label = 'Pump Housing'): DocumentSnapshot => { return document } -const selectionRequired = new Set(['pad', 'pocket', 'revolution', 'fillet', 'chamfer', 'union', 'cut', 'intersection', 'hole', 'linear-pattern', 'polar-pattern', 'measure-distance', 'measure-angle', 'measure-area', 'solve-sketch']) +const selectionRequired = new Set(['pad', 'pocket', 'revolution', 'fillet', 'chamfer', 'union', 'cut', 'intersection', 'check-shape', 'hole', 'linear-pattern', 'polar-pattern', 'measure-distance', 'measure-angle', 'measure-area', 'solve-sketch']) const systemCommands = new Set(['new-document', 'save', 'select-object']) -const implementedCommandIds = new Set(['new-document', 'save', 'select-object', 'create-body', 'create-sketch', 'new-sketch', 'pad', 'pocket', 'revolution', 'fillet', 'chamfer', 'primitive', 'union', 'cut', 'intersection', 'solve-sketch']) +const implementedCommandIds = new Set(['new-document', 'save', 'select-object', 'create-body', 'create-sketch', 'new-sketch', 'pad', 'pocket', 'revolution', 'fillet', 'chamfer', 'primitive', 'union', 'cut', 'intersection', 'check-shape', 'solve-sketch']) const partDesignCommands = new Set(['create-body', 'create-sketch', 'pad', 'pocket', 'revolution', 'fillet', 'chamfer']) -const partCommands = new Set(['primitive', 'union', 'cut', 'intersection']) +const partCommands = new Set(['primitive', 'union', 'cut', 'intersection', 'check-shape']) const featureCommands: Record = { 'create-body': { label: 'Body', detail: 'Part Design body' }, 'create-sketch': { label: 'Sketch', detail: 'Fully constrained' }, @@ -504,6 +504,21 @@ export function createMockFacade(): BitBybitWebCadFacade { }) } else if (commandId === 'select-object' && typeof payload?.objectId === 'string') select(payload.objectId) + else if (commandId === 'check-shape') { + const objectId = state.selectedObjectId + const shape = featureShapes.get(objectId) + const reportFailure = (code: string, message: string) => { + const diagnostic: Diagnostic = { id: `diag-${++requestSequence}`, severity: 'warning', code, message, objectId, requestId } + state = { ...state, diagnostics: [...state.diagnostics, diagnostic] } + emit({ type: 'diagnostic.added', diagnostic, context }) + notify(message) + } + if (!shape) reportFailure('SHAPE_NOT_RECOMPUTED', `No valid Shape is cached for ${objectId}; recompute the document first.`) + else void geometryRuntime.mesh(shape, 0.05).then((mesh) => { + if (mesh.indices.length === 0 || mesh.positions.length === 0) throw new Error('Shape mesh is empty.') + notify(`Shape check passed: ${mesh.subshapes?.length || 0} subshapes`) + }).catch((error: unknown) => reportFailure('SHAPE_CHECK_FAILED', error instanceof Error ? error.message : String(error))) + } else if (commandId === 'solve-sketch') { solveSketchObject(state.selectedObjectId); notify('Sketch solver completed') } else if (commandId === 'new-sketch') beginTask('create-sketch', { source: state.selectedObjectId || null }) else if (featureCommands[commandId]) beginTask(commandId, { source: state.selectedObjectId || null }) diff --git a/tests/facade.test.ts b/tests/facade.test.ts index 5685203..8c95990 100644 --- a/tests/facade.test.ts +++ b/tests/facade.test.ts @@ -787,6 +787,9 @@ test('Part workbench commands create primitive and boolean document objects', () const facade = createMockFacade() facade.gui.workbench.setActive('Part') assert.equal(facade.gui.command.getState('primitive').status, 'enabled') + assert.equal(facade.gui.command.getState('check-shape').status, 'enabled') + facade.gui.command.execute({ commandId: 'check-shape' }) + assert.equal(facade.getState().diagnostics.at(-1)?.code, 'SHAPE_NOT_RECOMPUTED') facade.gui.command.execute({ commandId: 'primitive' }) facade.task.apply() assert.equal(facade.app.document.getObject('box')?.typeId, 'Part::Box')