P3-02 P5-02: integrate Bitbybit OCCT geometry runtime

This commit is contained in:
2026-08-02 06:45:49 -04:00
parent feb2eb8be6
commit 9f2d11e151
14 changed files with 454 additions and 12 deletions

View File

@@ -53,7 +53,7 @@ import {
ZoomOut,
} from 'lucide-react'
import { menuDefinitions, pinnedWorkbenches, workbenchDefinitions, type MenuName, type WorkbenchId } from './freecadManifest'
import { createMockFacade, type BitBybitWebCadFacade, type DocumentSnapshot, type ModelTreeItem } from './facade'
import { createMockFacade, type BitBybitViewportAdapter, type BitBybitWebCadFacade, type DocumentSnapshot, type ModelTreeItem, type ShapeHandle } from './facade'
type Page = 'start' | 'projects' | 'workspace' | 'import' | 'export' | 'settings' | 'help' | 'diagnostics' | 'sync'
type Workbench = WorkbenchId
@@ -135,6 +135,8 @@ function App() {
}
}), [facade])
useEffect(() => () => facade.geometry.dispose(), [facade])
useEffect(() => {
const handlePopState = () => setPage(pageFromPath())
window.addEventListener('popstate', handlePopState)
@@ -357,20 +359,47 @@ function ViewProperties({ showNotice }: { showNotice: (message: string) => void
function Viewport({ selectedObject, setSelectedObject, workbench, facade, showNotice }: { selectedObject: string; setSelectedObject: (id: string) => void; workbench: Workbench; facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
const hostRef = useRef<HTMLDivElement>(null)
const adapterRef = useRef<BitBybitViewportAdapter | null>(null)
useEffect(() => {
const host = hostRef.current
if (!host) return
const adapter = facade.viewport.createAdapter()
adapterRef.current = adapter
let cancelled = false
let shape: ShapeHandle | null = null
try {
adapter.mount(host)
adapter.setSelection(selectedObject)
return () => adapter.dispose()
} catch (error) {
showNotice(`WebGL viewport unavailable: ${error instanceof Error ? error.message : 'unknown error'}`)
adapter.dispose()
adapterRef.current = null
return
}
}, [facade, showNotice, selectedObject])
return <div className="viewport"><div className="viewport-header"><div className="viewport-title"><span className="eyebrow">{workbench}</span><strong>Body / Fillet</strong></div><div className="viewport-actions"><IconButton icon={ZoomOut} label="Zoom out" /><IconButton icon={ZoomIn} label="Zoom in" /><IconButton icon={Rotate3D} label="Orbit view" active /></div></div><div className="viewport-grid" onClick={() => setSelectedObject('')}><div className="three-viewport-host" ref={hostRef} aria-label="Three.js viewport" /><div className="grid-lines" /><div className={`mock-part ${selectedObject === 'pad' || selectedObject.startsWith('pad') ? 'part-selected' : ''}`} onClick={(event) => { event.stopPropagation(); setSelectedObject('pad'); showNotice('Selected Pad') }}><div className="part-top" /><div className="part-front"><span className="part-hole one" /><span className="part-hole two" /><span className="part-cut" /></div><div className="part-side" /></div><div className="mock-datum-plane" /><div className="selection-callout"><span className="callout-line" /><span>Pad</span></div></div><div className="viewport-legend"><span><span className="legend-swatch selected" />Selected</span><span><span className="legend-swatch edge" />Edges</span><span><span className="legend-swatch datum" />Datum</span></div></div>
const loadGeometry = async () => {
const capabilities = await facade.geometry.initialize()
if (capabilities.status !== 'ready') throw new Error(capabilities.reason || 'OCCT geometry runtime unavailable')
shape = await facade.geometry.createBox({ width: 2.7, length: 1.4, height: 1.6, center: [0, 0, 0], originOnCenter: true, documentVersion: facade.app.document.getActive().version })
if (cancelled) {
await facade.geometry.release(shape)
shape = null
return
}
const mesh = await facade.geometry.mesh(shape, 0.05)
if (!cancelled) adapter.setMesh(mesh)
}
void loadGeometry().catch((error: unknown) => {
if (!cancelled) showNotice(`Geometry preview unavailable: ${error instanceof Error ? error.message : String(error)}`)
})
return () => {
cancelled = true
if (shape) void facade.geometry.release(shape)
adapter.dispose()
adapterRef.current = null
}
}, [facade, showNotice])
useEffect(() => adapterRef.current?.setSelection(selectedObject), [selectedObject])
return <div className="viewport"><div className="viewport-header"><div className="viewport-title"><span className="eyebrow">{workbench}</span><strong>Body / Fillet</strong></div><div className="viewport-actions"><IconButton icon={ZoomOut} label="Zoom out" /><IconButton icon={ZoomIn} label="Zoom in" /><IconButton icon={Rotate3D} label="Orbit view" active /></div></div><div className="viewport-grid" onClick={() => setSelectedObject('')}><div className="three-viewport-host" ref={hostRef} aria-label="Three.js viewport" /></div><div className="viewport-legend"><span><span className="legend-swatch selected" />Selected</span><span><span className="legend-swatch edge" />Edges</span><span><span className="legend-swatch datum" />Datum</span></div></div>
}
function PageFrame({ page, onNavigate, onOpenWorkspace, showNotice }: { page: Page; onNavigate: (page: Page) => void; onOpenWorkspace: () => void; showNotice: (message: string) => void }) {