Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/editor/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ export {
editorHostPanelRegistry,
registerEditorHostPanel,
} from './lib/plugin-panels'
export { configureManifoldRuntime } from './lib/print-shell-compiler-manifold-worker'
export type { ManifoldRuntimeOptions } from './lib/print-shell-compiler-protocol'
export {
createQuickMeasurementPointerScheduler,
quickMeasurementContext,
Expand Down
69 changes: 59 additions & 10 deletions packages/editor/src/lib/print-shell-compiler-manifold-core.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,70 @@
import ManifoldModule, { type Manifold as ManifoldSolid, type ManifoldToplevel } from 'manifold-3d'
import type { Manifold as ManifoldSolid, ManifoldToplevel } from 'manifold-3d'
import type { PrintShellCompileDiagnostic } from './print-shell-compiler-baseline'
import type { ManifoldCompileOutput, ManifoldMeshData } from './print-shell-compiler-protocol'
import type {
ManifoldCompileOutput,
ManifoldMeshData,
ManifoldRuntimeOptions,
} from './print-shell-compiler-protocol'

let modulePromise: Promise<ManifoldToplevel> | null = null
const MANIFOLD_OUTPUT_WELD_EPSILON_METERS = 2e-5
const COLLINEAR_SEAM_CROSS_LENGTH_SQ = 1e-20

type Triangle = [number, number, number]

async function getManifoldModule(wasmUrl?: string): Promise<ManifoldToplevel> {
modulePromise ??= ManifoldModule(wasmUrl ? { locateFile: () => wasmUrl } : undefined).then(
(module) => {
type ManifoldFactory = (config?: {
locateFile?: (path: string) => string
}) => Promise<ManifoldToplevel>

// manifold-3d's emscripten glue awaits import('node:module') behind a Node
// check. The branch never executes in a browser, but webpack refuses to
// *build* a graph that can reach it, so a static specifier here poisons every
// external bundler that compiles this package's source (#715). The factory is
// therefore loaded through an import() no bundler can trace: the bare
// specifier resolves wherever node-style resolution exists at runtime (bun
// tests, dev servers, bundlers that inline it anyway), and the version-pinned
// CDN copy covers bundled browser builds that left the specifier unresolved —
// emscripten then locates manifold.wasm relative to the glue's own URL. Hosts
// that can't reach the CDN (offline, CSP) pass their own URLs through
// configureManifoldRuntime.
const MANIFOLD_VERSION = '3.5.1'
const FALLBACK_MODULE_URL = `https://cdn.jsdelivr.net/npm/manifold-3d@${MANIFOLD_VERSION}/manifold.js`

function importUntraced(specifier: string): Promise<{ default: ManifoldFactory }> {
return import(/* webpackIgnore: true */ /* @vite-ignore */ specifier)
}

async function loadManifoldFactory(moduleUrl?: string): Promise<ManifoldFactory> {
const specifiers = moduleUrl ? [moduleUrl] : ['manifold-3d', FALLBACK_MODULE_URL]
let lastError: unknown
for (const specifier of specifiers) {
try {
return (await importUntraced(specifier)).default
} catch (error) {
lastError = error
}
}
throw lastError instanceof Error ? lastError : new Error('Failed to load manifold-3d.')
}

async function getManifoldModule(runtime?: ManifoldRuntimeOptions): Promise<ManifoldToplevel> {
modulePromise ??= loadManifoldFactory(runtime?.moduleUrl)
.then((factory) => {
const wasmUrl = runtime?.wasmUrl
return factory(wasmUrl ? { locateFile: () => wasmUrl } : undefined)
})
.then((module) => {
module.setup()
return module
},
)
return modulePromise
})
try {
return await modulePromise
} catch (error) {
// A transient load failure (offline, blocked CDN) must not poison every
// later compile attempt with the cached rejection.
modulePromise = null
throw error
}
}

function manifoldMesh(
Expand Down Expand Up @@ -211,7 +260,7 @@ function elapsed(startedAt: number): number {

export async function compileManifoldMeshData(
meshes: ManifoldMeshData[],
wasmUrl?: string,
runtime?: ManifoldRuntimeOptions,
): Promise<ManifoldCompileOutput> {
const startedAt = performance.now()
const sourceNodeIds = Array.from(new Set(meshes.map((mesh) => mesh.nodeId))).sort()
Expand All @@ -238,7 +287,7 @@ export async function compileManifoldMeshData(
}

try {
const module = await getManifoldModule(wasmUrl)
const module = await getManifoldModule(runtime)
for (const mesh of meshes) {
try {
solids.push(new module.Manifold(manifoldMesh(module, mesh)))
Expand Down
15 changes: 14 additions & 1 deletion packages/editor/src/lib/print-shell-compiler-manifold-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,25 @@ import {
import type {
ManifoldCompileOutput,
ManifoldMeshData,
ManifoldRuntimeOptions,
ManifoldWorkerRequest,
ManifoldWorkerResponse,
} from './print-shell-compiler-protocol'

const WORKER_TIMEOUT_MS = 60_000

let manifoldRuntime: ManifoldRuntimeOptions | undefined

/**
* Overrides where the print-export worker loads the manifold-3d module and
* wasm from. See the loader in print-shell-compiler-manifold-core.ts for the
* default resolution order; hosts with restrictive networks should call this
* with self-hosted asset URLs before the first print export.
*/
export function configureManifoldRuntime(options: ManifoldRuntimeOptions | undefined): void {
manifoldRuntime = options
}

export type ManifoldCompileRunner = (meshes: ManifoldMeshData[]) => Promise<ManifoldCompileOutput>

export type SemanticManifoldCompileOptions = SemanticPrintCompileOptions & {
Expand Down Expand Up @@ -76,7 +89,7 @@ export const runManifoldWorker: ManifoldCompileRunner = (meshes) => {
const activeWorker = getWorker()
const id = nextRequestId
nextRequestId += 1
const request: ManifoldWorkerRequest = { id, meshes }
const request: ManifoldWorkerRequest = { id, meshes, runtime: manifoldRuntime }
const transfer = meshes.flatMap((mesh) => [
mesh.positions.buffer as ArrayBuffer,
mesh.indices.buffer as ArrayBuffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const workerScope = self as unknown as {
}

workerScope.addEventListener('message', async (event) => {
const output = await compileManifoldMeshData(event.data.meshes)
const output = await compileManifoldMeshData(event.data.meshes, event.data.runtime)
const response: ManifoldWorkerResponse = { id: event.data.id, ...output }
const transfer: Transferable[] = []
if (response.status === 'compiled') {
Expand Down
8 changes: 8 additions & 0 deletions packages/editor/src/lib/print-shell-compiler-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,17 @@ export type ManifoldCompileOutput =
durationMs: number
}

export type ManifoldRuntimeOptions = {
/** URL of the manifold-3d emscripten glue module. Defaults to the pinned CDN copy. */
moduleUrl?: string
/** URL of manifold.wasm. Defaults to resolving relative to the glue module. */
wasmUrl?: string
}

export type ManifoldWorkerRequest = {
id: number
meshes: ManifoldMeshData[]
runtime?: ManifoldRuntimeOptions
}

export type ManifoldWorkerResponse = ManifoldCompileOutput & { id: number }
Loading