feat: advance FreeCAD parity verification and runtime boundaries
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-11 02:14:45 -04:00
parent 26ea9b57d7
commit 2be18a511e
31 changed files with 451 additions and 57 deletions

View File

@@ -8,6 +8,16 @@ export type BodyFeatureState = {
solid: boolean
}
const nonSolidBodyTypeIds = new Set([
'Sketcher::SketchObject',
'PartDesign::Body',
'PartDesign::Plane',
'PartDesign::Line',
'PartDesign::Point',
'PartDesign::ShapeBinder',
'PartDesign::SubShapeBinder',
])
export const resolveBodyTip = (features: readonly BodyFeatureState[]): string | null => {
for (let index = features.length - 1; index >= 0; index -= 1) {
const feature = features[index]
@@ -29,7 +39,7 @@ export const redirectBodyTips = (document: DocumentSnapshot, objectStates: Recor
typeId: object?.typeId ?? '',
suppressed: objectStates[id] === 'suppressed',
upstreamSuppressed: objectStates[id] === 'upstream-suppressed',
solid: Boolean(object && object.typeId !== 'Sketcher::SketchObject' && object.typeId !== 'PartDesign::Body'),
solid: Boolean(object && !nonSolidBodyTypeIds.has(object.typeId)),
}
})
const nextTip = resolveBodyTip(features)

View File

@@ -238,6 +238,11 @@ const recognizedTypeIds = new Set([
'PartDesign::LinearPattern',
'PartDesign::PolarPattern',
'PartDesign::Hole',
'PartDesign::Plane',
'PartDesign::Line',
'PartDesign::Point',
'PartDesign::ShapeBinder',
'PartDesign::SubShapeBinder',
'Sketcher::SketchObject',
'Path::Feature',
])

View File

@@ -1,5 +1,7 @@
export { createBlankDocument, createMockFacade, createPumpHousingDemoDocument, createWebCadFacade } from './mockFacade'
export type { WebCadFacadeOptions } from './mockFacade'
export { createFacadeRuntimeProfile } from './runtimeProfile'
export type { FacadeRuntimeMode, FacadeRuntimeProfile } from './runtimeProfile'
export { createProductionFacade } from './productionFacade'
export type { ProductionFacadeOptions } from './productionFacade'
export { buildDiagnosticTree, buildRecomputeDiagnostics } from './diagnostics'
@@ -75,7 +77,8 @@ export { createNativeOcctStepHistoryBridge, mapNativeOcctHistoryRecords } from '
export type { NativeOcctHistoryOperation, NativeOcctHistoryRecord, NativeOcctHistoryResponse, NativeOcctHistoryStage, NativeOcctHistoryStepGeometry, NativeOcctHistoryStepProvider } from './nativeHistoryProvider'
export { DirectNativeOcctHistoryProvider, NativeOcctHistoryCoordinator, NativeOcctHistoryUnavailableError, UnavailableNativeOcctHistoryProvider } from './nativeHistoryProtocol'
export { NATIVE_OCCT_HISTORY_PROTOCOL_VERSION } from './nativeHistoryProtocol'
export type { NativeOcctHistoryCapabilities, NativeOcctHistoryExecution, NativeOcctHistoryInputTransport, NativeOcctHistoryProvider, NativeOcctHistoryProtocolResponse, NativeOcctHistoryRequest, NativeOcctHistoryStageTransport } from './nativeHistoryProtocol'
export { NATIVE_OCCT_NAMING_ABI_UNAVAILABLE, nativeNamingAbiCapabilities } from './nativeHistoryProtocol'
export type { NativeNamingAbiCapabilities, NativeOcctHistoryCapabilities, NativeOcctHistoryExecution, NativeOcctHistoryInputTransport, NativeOcctHistoryProvider, NativeOcctHistoryProtocolResponse, NativeOcctHistoryRequest, NativeOcctHistoryStageTransport } from './nativeHistoryProtocol'
export { assertNativeNamingEvidence, createFinalShapeOnlyNamingEvidence, createNativeStageNamingEvidence, hasNativeMappedNameEvidence, validateNativeNamingEvidence } from './nativeNamingEvidence'
export type { NativeMappedNameRef, NativeMappedNameRelation, NativeNamingEvidenceIssue, NativeNamingEvidenceReport, NativeNamingEvidenceStatus, NativeStageNamingEvidence } from './nativeNamingEvidence'
export { NativeOcctHistoryWorkerProvider } from './nativeHistoryWorkerClient'

View File

@@ -31,6 +31,7 @@ import type {
TopoRefValue,
Unsubscribe,
} from './types'
import { createFacadeRuntimeProfile, type FacadeRuntimeMode } from './runtimeProfile'
import { ATTACHMENT_MAP_MODES, validateAttachmentOffset, validateAttachmentSupport } from './attachment'
import { redirectBodyTips } from './bodyRules'
import { createSqliteProjectPersistence, ProjectAutosaveScheduler } from './projectStore'
@@ -315,7 +316,7 @@ const collectDependencyEdges = (document: Pick<DocumentSnapshot, 'objects'>): De
if (property.type === 'App::PropertyLinkList' && Array.isArray(property.value)) for (const [index, targetId] of property.value.entries()) {
if (typeof targetId === 'string' && objectIds.has(targetId)) edges.push({ sourceId: object.id, targetId, relation: 'link', propertyName: property.name, reference: `${property.name}[${index}]` })
}
if (property.name === 'Support' && property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'objectId' in property.value && typeof property.value.objectId === 'string' && objectIds.has(property.value.objectId)) {
if (property.type === 'App::PropertyLink' && property.name === 'Support' && property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'objectId' in property.value && typeof property.value.objectId === 'string' && objectIds.has(property.value.objectId)) {
const subElement = 'subElement' in property.value ? property.value.subElement : null
if (subElement && typeof subElement === 'object' && !Array.isArray(subElement) && 'schemaVersion' in subElement && typeof subElement.persistentId === 'string') edges.push({ sourceId: object.id, targetId: property.value.objectId, relation: 'topo-ref', propertyName: property.name, reference: subElement.persistentId })
else edges.push({ sourceId: object.id, targetId: property.value.objectId, relation: 'link', propertyName: property.name })
@@ -386,15 +387,16 @@ export type WebCadFacadeOptions = {
initialDocument?: DocumentSnapshot
initialWorkbench?: WorkbenchId
initialSelectedObjectIds?: string[]
runtimeMode?: FacadeRuntimeMode
}
const selectionRequired = new Set(['pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'extrude-part', 'revolution-part', 'loft-part', 'sweep-part', 'union', 'cut', 'intersection', 'check-shape', 'hole', 'linear-pattern', 'polar-pattern', 'measure-distance', 'measure-angle', 'measure-area', 'solve-sketch'])
const selectionRequired = new Set(['pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'shape-binder', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'extrude-part', 'revolution-part', 'loft-part', 'sweep-part', '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 camCommandIds = new Set(workbenchDefinitions.CAM.groups.flatMap((group) => group.commands.map((entry) => entry.id)))
const camSimulationCommands = new Set(['CAM_SimTools', 'CAM_Simulator', 'CAM_SimulatorGL'])
const camCopyCommands = new Set(['CAM_OperationCopy', 'CAM_Copy', 'CAM_SimpleCopy'])
const implementedCommandIds = new Set(['new-document', 'save', 'select-object', 'create-body', 'create-sketch', 'new-sketch', 'pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'linear-pattern', 'polar-pattern', 'hole', 'primitive', 'helix', 'extrude-part', 'revolution-part', 'loft-part', 'sweep-part', 'union', 'cut', 'intersection', 'fillet-part', 'chamfer-part', 'check-shape', 'solve-sketch', ...camCommandIds])
const partDesignCommands = new Set(['create-body', 'create-sketch', 'pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'linear-pattern', 'polar-pattern', 'hole'])
const implementedCommandIds = new Set(['new-document', 'save', 'select-object', 'create-body', 'create-sketch', 'new-sketch', 'shape-binder', 'datum', 'pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'linear-pattern', 'polar-pattern', 'hole', 'primitive', 'helix', 'extrude-part', 'revolution-part', 'loft-part', 'sweep-part', 'union', 'cut', 'intersection', 'fillet-part', 'chamfer-part', 'check-shape', 'solve-sketch', ...camCommandIds])
const partDesignCommands = new Set(['create-body', 'create-sketch', 'shape-binder', 'datum', 'pad', 'pocket', 'revolution', 'groove', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe', 'fillet', 'chamfer', 'draft', 'thickness', 'mirrored', 'multi-transform', 'linear-pattern', 'polar-pattern', 'hole'])
const partCommands = new Set(['primitive', 'helix', 'extrude-part', 'revolution-part', 'loft-part', 'sweep-part', 'union', 'cut', 'intersection', 'fillet-part', 'chamfer-part', 'check-shape'])
const partProfileCommands = new Set(['extrude-part', 'revolution-part', 'loft-part', 'sweep-part', 'additive-loft', 'subtractive-loft', 'additive-pipe', 'subtractive-pipe'])
const loftCommands = new Set(['loft-part', 'additive-loft', 'subtractive-loft'])
@@ -447,6 +449,8 @@ const shapeTypeIds = new Set([
const featureCommands: Record<string, { label: string; detail: string }> = {
'create-body': { label: 'Body', detail: 'Part Design body' },
'create-sketch': { label: 'Sketch', detail: 'Fully constrained' },
'shape-binder': { label: 'Shape Binder', detail: 'Cross-body support reference' },
datum: { label: 'Datum Plane', detail: 'Attached datum reference' },
pad: { label: 'Pad', detail: 'Length 42 mm' },
pocket: { label: 'Pocket', detail: 'Through all' },
revolution: { label: 'Revolution', detail: 'Angle 360 deg' },
@@ -675,6 +679,7 @@ const markDocumentTouched = (document: DocumentSnapshot, objectIds: Iterable<str
}
export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitWebCadFacade {
const runtime = createFacadeRuntimeProfile(options.runtimeMode ?? 'production')
const projectPersistence = createSqliteProjectPersistence()
const geometryRuntime = new BitbybitGeometryRuntime()
const camJob = createCamJob('Job', 'CAM Job', { min: [0, 0, 0], max: [10, 10, 10], mode: 'from-base-bound-box' })
@@ -743,26 +748,40 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
}
const appendFeature = (document: DocumentSnapshot, commandId: string, draft: Record<string, unknown> = {}): { document: DocumentSnapshot; objectId: string } => {
const primitiveType = commandId === 'primitive' && ['Box', 'Cylinder', 'Sphere', 'Ellipsoid', 'Cone', 'Torus', 'Prism', 'Wedge'].includes(String(draft.primitiveType)) ? String(draft.primitiveType) : 'Box'
const definition = commandId === 'primitive' ? { label: primitiveType, detail: primitiveType === 'Box' ? '10 × 10 × 10 mm' : `${primitiveType} primitive` } : featureCommands[commandId]
const requestedDatumType = draft.datumType === 'Line' || draft.datumType === 'Point' ? draft.datumType : 'Plane'
const definition = commandId === 'primitive'
? { label: primitiveType, detail: primitiveType === 'Box' ? '10 × 10 × 10 mm' : `${primitiveType} primitive` }
: commandId === 'datum'
? { label: `Datum ${requestedDatumType}`, detail: 'Attached datum reference' }
: featureCommands[commandId]
if (!definition) return { document, objectId: '' }
const objectId = nextFeatureId(definition.label)
const type = commandId === 'create-sketch' ? 'sketch' : commandId === 'create-body' ? 'body' : 'feature'
const item: ModelTreeItem = { id: objectId, label: definition.label, type, state: type === 'body' ? 'active' : 'valid', detail: definition.detail }
const tree: ModelTreeItem[] = document.tree.map((entry) => ({ ...entry, children: entry.children ? [...entry.children] : undefined }))
const isPartObject = partCommands.has(commandId)
if (type === 'body' || isPartObject) tree.push({ ...item, children: type === 'body' ? [] : undefined })
if (type === 'body') {
for (const body of tree.filter((entry) => entry.type === 'body')) if (body.state === 'active') body.state = 'valid'
tree.push({ ...item, children: [] })
} else if (isPartObject) tree.push({ ...item, children: undefined })
else {
const body = tree.find((entry) => entry.type === 'body')
const body = [...tree].reverse().find((entry) => entry.type === 'body' && entry.state === 'active') ?? tree.find((entry) => entry.type === 'body')
if (body) body.children = [...(body.children || []), objectId]
tree.push(item)
}
const objectSnapshot = createObjectSnapshot(item)
if (commandId === 'create-body') {
const tip = objectSnapshot.properties.find((property) => property.name === 'Tip')
if (tip) tip.value = null
}
const sourceId = typeof draft.source === 'string' && document.objects.some((object) => object.id === draft.source) ? draft.source : ''
const sourceObject = sourceId ? document.objects.find((object) => object.id === sourceId) : undefined
const currentSubshape = state.selectedSubshape
const selectedEdge = currentSubshape && currentSubshape.objectId === sourceObject?.id && currentSubshape.ref.kind === 'edge' ? currentSubshape : null
const selectedFace = currentSubshape && currentSubshape.objectId === sourceObject?.id && currentSubshape.ref.kind === 'face' ? currentSubshape : null
const bodyTipValue = document.objects.find((object) => object.typeId === 'PartDesign::Body')?.properties.find((property) => property.name === 'Tip')?.value
const activeBodyItem = [...tree].reverse().find((entry) => entry.type === 'body' && entry.state === 'active') ?? tree.find((entry) => entry.type === 'body')
if (['datum', 'shape-binder'].includes(commandId) && !activeBodyItem) throw new Error(`${definition.label} requires an active Body.`)
const bodyTipValue = document.objects.find((object) => object.id === activeBodyItem?.id)?.properties.find((property) => property.name === 'Tip')?.value
const bodyTip = typeof bodyTipValue === 'string' && document.objects.some((object) => object.id === bodyTipValue && shapeTypeIds.has(object.typeId)) ? bodyTipValue : null
const sectionDraft = Array.isArray(draft.sections) && draft.sections.every((entry) => typeof entry === 'string') ? [...draft.sections] as string[] : sourceObject?.typeId === 'Sketcher::SketchObject' ? [sourceObject.id] : []
const pipeProfile = typeof draft.profile === 'string' ? draft.profile : sourceObject?.typeId === 'Sketcher::SketchObject' ? sourceObject.id : null
@@ -789,6 +808,33 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
signature: selectedFace.ref.signature,
candidates: selectedFace.ref.candidates ? [...selectedFace.ref.candidates] : undefined,
} : null
if (commandId === 'datum') {
const datumType = requestedDatumType
const typeId = `PartDesign::${datumType}`
const support = selectedFaceRef ?? selectedEdgeRef ?? (sourceObject ? { schemaVersion: 1 as const, objectId: sourceObject.id, subElements: [] } : null)
objectSnapshot.typeId = typeId
objectSnapshot.properties = [
...commonProperties({ ...item, label: `Datum ${datumType}` }).map((property) => property.name === 'TypeId' ? { ...property, value: typeId } : property),
{ name: 'Support', label: 'Support', group: 'Attachment', scope: 'data', type: 'App::PropertyLinkSub', value: support, recompute: true },
{ name: 'MapMode', label: 'Map mode', group: 'Attachment', scope: 'data', type: 'App::PropertyEnumeration', value: selectedFaceRef ? 'FlatFace' : selectedEdgeRef ? 'NormalToEdge' : support ? 'ObjectXY' : 'Deactivated', options: [...ATTACHMENT_MAP_MODES], recompute: true },
{ name: 'AttachmentOffset', label: 'Attachment offset', group: 'Attachment', scope: 'data', type: 'App::PropertyPlacement', value: { position: { x: 0, y: 0, z: 0 }, rotation: { axis: { x: 0, y: 0, z: 1 }, angle: 0 } }, recompute: true },
{ name: 'DatumType', label: 'Datum type', group: 'Datum', scope: 'data', type: 'App::PropertyEnumeration', value: datumType, options: ['Plane', 'Line', 'Point'], recompute: true },
...viewProperties(),
]
}
if (commandId === 'shape-binder') {
const support = selectedFaceRef ?? selectedEdgeRef ?? (sourceObject ? { schemaVersion: 1 as const, objectId: sourceObject.id, subElements: [] } : null)
if (!support) throw new RangeError('Shape Binder requires a source object or stable sub-shape.')
objectSnapshot.typeId = 'PartDesign::ShapeBinder'
objectSnapshot.properties = [
...commonProperties(item).map((property) => property.name === 'TypeId' ? { ...property, value: 'PartDesign::ShapeBinder' } : property),
{ name: 'Support', label: 'Support', group: 'Binder', scope: 'data', type: 'App::PropertyLinkSub', value: support, recompute: true },
{ name: 'BindMode', label: 'Bind mode', group: 'Binder', scope: 'data', type: 'App::PropertyEnumeration', value: 'Synchronized', options: ['Synchronized', 'Frozen'], recompute: true },
{ name: 'TraceSupport', label: 'Trace support', group: 'Binder', scope: 'data', type: 'App::PropertyBool', value: true, recompute: true },
{ name: 'ClaimChildren', label: 'Claim children', group: 'Binder', scope: 'data', type: 'App::PropertyBool', value: false, recompute: true },
...viewProperties(),
]
}
if (commandId === 'extrude-part') {
objectSnapshot.typeId = 'Part::Extrusion'
objectSnapshot.properties = [
@@ -963,7 +1009,7 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
if (Number(objectSnapshot.properties.find((property) => property.name === 'Depth')?.value) <= 0) throw new RangeError('Hole depth must be greater than zero.')
}
const objects = [...document.objects.map((object) => ({ ...object, properties: object.properties.map((property) => ({ ...property, value: clonePropertyValue(property.value) })), sketch: object.sketch ? cloneSketch(object.sketch) : undefined })), objectSnapshot]
const tipObject = type === 'feature' && partDesignCommands.has(commandId) && commandId !== 'create-sketch' ? objects.find((object) => object.id === 'body') : undefined
const tipObject = type === 'feature' && partDesignCommands.has(commandId) && !['create-sketch', 'datum', 'shape-binder'].includes(commandId) ? objects.find((object) => object.id === activeBodyItem?.id) : undefined
if (tipObject) {
const tip = tipObject.properties.find((property) => property.name === 'Tip')
if (tip) tip.value = objectId
@@ -1404,6 +1450,7 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
}
const facade: BitBybitWebCadFacade = {
runtime,
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, topology: object.topology ? cloneObjectTopologySnapshot(object.topology) : undefined } : null }, create: (label) => { recomputeCoordinator.cancel(); clearFeatureShapes(); commit({ ...state, document: nextBlankDocument(label), selectedObjectId: '', selectedObjectIds: [] }); return getState().document }, load: loadDocument, markDirty: () => { commit({ ...state, document: { ...state.document, dirty: true } }) }, setProperty, reorderBodyFeature, removeObject, resolveTopologyReference, 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, projectGeometry: projectSketch, carbonCopy: carbonCopySketch, 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), state.selectedSubshape), list: (workbench) => workbenchDefinitions[workbench].groups.flatMap((group) => group.commands), execute } },
@@ -1473,6 +1520,7 @@ export function createWebCadFacade(options: WebCadFacadeOptions = {}): BitBybitW
export function createMockFacade(): BitBybitWebCadFacade {
return createWebCadFacade({
runtimeMode: 'mock',
initialDocument: createPumpHousingDemoDocument(),
initialSelectedObjectIds: PUMP_HOUSING_DEMO_TEMPLATE.selectedObjectIds,
})

View File

@@ -2,6 +2,18 @@ import type { NativeOcctHistoryOperation, NativeOcctHistoryResponse, NativeOcctH
export const NATIVE_OCCT_HISTORY_PROTOCOL_VERSION = 1 as const
export type NativeNamingAbiCapabilities = {
mappedNameRef: 'available' | 'optional' | 'unavailable'
stringHasher: 'available' | 'opaque-preserved' | 'unavailable'
tokenGeneration: 'native-only' | 'forbidden'
}
export const NATIVE_OCCT_NAMING_ABI_UNAVAILABLE: NativeNamingAbiCapabilities = Object.freeze({
mappedNameRef: 'unavailable',
stringHasher: 'unavailable',
tokenGeneration: 'forbidden',
})
export type NativeOcctHistoryCapabilities = {
providerId: string
providerVersion: string
@@ -9,9 +21,16 @@ export type NativeOcctHistoryCapabilities = {
availability: 'available' | 'unavailable'
operations: NativeOcctHistoryOperation[]
transport: 'step-text'
/** Private FreeCAD naming callbacks are intentionally never synthesized by this bridge. */
naming?: NativeNamingAbiCapabilities
reason?: string
}
export const nativeNamingAbiCapabilities = (capabilities: NativeOcctHistoryCapabilities): NativeNamingAbiCapabilities => ({
...NATIVE_OCCT_NAMING_ABI_UNAVAILABLE,
...(capabilities.naming ?? {}),
})
export type NativeOcctHistoryRequest = {
protocolVersion: typeof NATIVE_OCCT_HISTORY_PROTOCOL_VERSION
requestId: string

View File

@@ -1,7 +1,7 @@
/// <reference lib="webworker" />
import type { NativeOcctHistoryStepProvider } from './nativeHistoryProvider'
import type { NativeOcctHistoryCapabilities, NativeOcctHistoryRequest, NativeOcctHistoryProtocolResponse } from './nativeHistoryProtocol'
import { NATIVE_OCCT_NAMING_ABI_UNAVAILABLE, type NativeOcctHistoryCapabilities, type NativeOcctHistoryRequest, type NativeOcctHistoryProtocolResponse } from './nativeHistoryProtocol'
type WorkerRequest = { type: 'initialize'; moduleUrl: string } | { type: 'capture'; request: NativeOcctHistoryRequest } | { type: 'cancel'; requestId: string } | { type: 'dispose' }
type WorkerResponse = { type: 'ready'; capabilities: NativeOcctHistoryCapabilities } | { type: 'response'; response: NativeOcctHistoryProtocolResponse } | { type: 'error'; requestId?: string; error: string }
@@ -32,6 +32,7 @@ const initialize = async (moduleUrl: string) => {
availability: 'available',
operations: ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'],
transport: 'step-text',
naming: NATIVE_OCCT_NAMING_ABI_UNAVAILABLE,
},
})
}
@@ -80,7 +81,7 @@ scope.onmessage = ({ data }: MessageEvent<WorkerRequest>) => {
: provider.booleanHistoryFromStep(request.objectStep, request.toolStep || '', request.operation)
if (!history) throw new Error('Native OCCT history provider does not expose the requested feature operation.')
if (cancelled.delete(data.request.requestId)) return
send({ type: 'response', response: { protocolVersion: request.protocolVersion, requestId: request.requestId, documentId: request.documentId, documentVersion: request.documentVersion, operationId: request.operationId, provider: { providerId: 'occt-native.history-step', providerVersion: '8.0.0-embind', occtVersion: history.occtVersion, availability: 'available', operations: ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'], transport: 'step-text' }, history } })
send({ type: 'response', response: { protocolVersion: request.protocolVersion, requestId: request.requestId, documentId: request.documentId, documentVersion: request.documentVersion, operationId: request.operationId, provider: { providerId: 'occt-native.history-step', providerVersion: '8.0.0-embind', occtVersion: history.occtVersion, availability: 'available', operations: ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform'], transport: 'step-text', naming: NATIVE_OCCT_NAMING_ABI_UNAVAILABLE }, history } })
} catch (error) {
send({ type: 'error', requestId: data.type === 'capture' ? data.request.requestId : undefined, error: error instanceof Error ? error.message : String(error) })
}

View File

@@ -1,8 +1,8 @@
import { createWebCadFacade, type WebCadFacadeOptions } from './mockFacade'
import { createWebCadFacade, type WebCadFacadeOptions } from './webCadFacade'
import type { BitBybitWebCadFacade } from './types'
export type ProductionFacadeOptions = WebCadFacadeOptions
export function createProductionFacade(options: ProductionFacadeOptions = {}): BitBybitWebCadFacade {
return createWebCadFacade(options)
return createWebCadFacade({ ...options, runtimeMode: 'production' })
}

View File

@@ -0,0 +1,39 @@
export type FacadeRuntimeMode = 'production' | 'mock'
export type FacadeRuntimeProfile = {
mode: FacadeRuntimeMode
geometry: {
engine: 'bitbybit-occt'
execution: 'facade-runtime'
compatibility: 'none' | 'demo-bootstrap'
}
nativeHistory: {
transport: 'optional-worker'
availability: 'configured-at-application-boundary' | 'not-configured'
}
naming: {
nativeAbi: 'not-exposed'
preservation: 'opaque-preserved'
}
persistence: 'sqlite-opfs-with-memory-fallback'
viewport: 'three-webgl2'
}
export const createFacadeRuntimeProfile = (mode: FacadeRuntimeMode): FacadeRuntimeProfile => ({
mode,
geometry: {
engine: 'bitbybit-occt',
execution: 'facade-runtime',
compatibility: mode === 'mock' ? 'demo-bootstrap' : 'none',
},
nativeHistory: {
transport: 'optional-worker',
availability: 'not-configured',
},
naming: {
nativeAbi: 'not-exposed',
preservation: 'opaque-preserved',
},
persistence: 'sqlite-opfs-with-memory-fallback',
viewport: 'three-webgl2',
})

View File

@@ -5,6 +5,7 @@ import type { SketchConstraint, SketchExternalGeometry, SketchGeometry, SketchSn
import type { RecomputeExecutionOptions, RecomputeExecutionResult } from './recomputeEngine'
import type { FcstdArchiveLimits, FcstdInspection, FcstdInstantiatedShape, FcstdPathEdit, FcstdShapeResourcePayload, FcstdStoredShapeResource, FcstdWriteOptions } from './fcstd'
import type { NativeOcctHistoryCapabilities, NativeOcctHistoryProvider } from './nativeHistoryProtocol'
import type { FacadeRuntimeProfile } from './runtimeProfile'
import type { CamApi } from './cam'
import type { NativeStageNamingEvidence } from './nativeNamingEvidence'
@@ -851,6 +852,7 @@ export type ExecuteCommandInput = {
}
export interface BitBybitWebCadFacade {
readonly runtime: FacadeRuntimeProfile
readonly app: {
document: {
getActive(): DocumentSnapshot

View File

@@ -0,0 +1,3 @@
// Stable production-facing entry for the shared Facade core.
export { createWebCadFacade } from './mockFacade'
export type { WebCadFacadeOptions } from './mockFacade'