Skip to content

โ† Back to the User Guide ยท Home ยท companion to Migrate & Compare ยท One Page InfoGraphics - Landscape

Two Apps, One Kernel

An ERP (iDempiere, Java) and a BIM/CAD authoring tool (Bonsai, the Blender IFC add-on, Python) are two independent, community-built, million-line-class stacks. We fold the working parts of both onto one SQLite-WASM + Three.js kernel, in one browser tab, with no server underneath either one. Here's the arithmetic โ€” measured today, sourced below, not asserted.

~26ร—
ERP fold
1,427,147 Java LOC (iDempiere) โ†’ 55,283 JS LOC
~4.5ร—
CAD fold
235,365 Python LOC (Bonsai add-on) โ†’ 52,267 JS LOC
~15.5ร—
Combined, one kernel
1,662,512 LOC (two stacks) โ†’ 107,550 LOC (one runtime)
iDempiere Java, by what it's for (1,427,147 LOC)
55,283 (3.9%) โ€” our fold
generated boilerplate OSGi/servlets/JDBC/reports M-class rules + processes ZK UI
Bonsai add-on, Python (235,365 LOC)
52,267 (22%) โ€” our fold
Bonsai add-on (no internal breakdown taken)
The server was never free. It was hidden โ€” inside a JVM, an OSGi module graph, and a ZK widget tree, standing between a click and a 3,300-line class.
Two apps, one kernel: an ERP and a BIM viewer don't need two servers when they already share one SQLite-WASM database and one render loop.
The ERP ratio keeps falling as real coverage grows โ€” 89ร—, 76ร—, 51ร—, now ~26ร— โ€” because ceremony doesn't come back when you add a feature. Only logic does.
~1% of the business logic ported, 100% of the offline behavior. The 99% that's gone was never the business โ€” it was how the business talked to a server.
The BIM community already asked this โ€” "Mr IFC and Mrs SQLite," OSArch, 2023 โ€” and set it down, keeping the Blender dependency. This is what happens if you don't set it down.
Which pile did which job โ€” and what stands in for it now
Legacy layer Its job Now standing in Why the layer disappears
JVM run the compiled classes the browser's own JS engine already on every device โ€” we ship none of it
OSGi 60 plugins, loaded/wired/versioned independently plain script includes one page, one process โ€” nothing to hot-swap
ZK UI (server widget tree + AJAX diff/patch) push state from server to screen Three.js scene graph + DOM state is already on the screen's device
Servlets / RPC + session mgmt carry every click to a server and back kernel oplog โ€” local, signed, hash-chained the write happens on-device; sync is async, off the interaction path
PostgreSQL server (WAL, pooling, replication) hold the Application Dictionary + transactions SQLite-WASM, IndexedDB-backed the DB ships as a file and runs in-process
Maven build + app server (3.7GB) package/deploy the JVM app static hosting (GH Pages / OCI) the files are the deployment โ€” no runtime build step
Blender's bpy/add-on registration, bmesh editing kernel host Bonsai's authoring tools Three.js WebGL scene + our own compiled-geometry DB same idea, no desktop install
Code, side by side โ€” a fold we've actually shipped (MOrder.completeIt())

Not a rewrite-from-scratch guess โ€” the same order-completion decision tree. The line drop is getX()/setX()/saveEx()/SQL/try-catch boilerplate falling away; the same four business decisions remain in both columns. Witness W-FOLD-COMPLETE, maxDiff=0c. Full block-for-block listing: ERP Rosetta Stone ยง3.

iDempiere ยท Java, ~250 LOC
if (!m_justPrepared) prepareIt();
if (fireDocValidate(BEFORE_COMPLETE) != null)
    return STATUS_Invalid;
if (!isApproved()) approveIt();
createCounterDoc();
shipment = createShipment(dt, getDateOrdered());
invoice  = createInvoice(dt, shipment, ...);
if (fireDocValidate(AFTER_COMPLETE) != null)
    return STATUS_Invalid;
setProcessed(true); setDocAction(Close);
return STATUS_Completed;
our fold ยท JS, ~50 LOC
if (CrudOverlay.docActionOutcome(entry,order).to!=='CO')
    return {status};
if (!AdModelVal.fireHooks('BEFORE_COMPLETE',{...}).ok)
    return {blocked};
const childOps = [ ...buildDoc('M_InOut',...),
                   ...buildDoc('C_Invoice',...) ];
const post = postRecipe('C_Order',order,lines)...;
const group = [DOC_ACTION_CO, {op:'POST',post}, ...childOps];
return KernelOps.commitGroup(db, group);
What this is not
  • Not feature parity, either side. Only ~1% of iDempiere's M-class business logic (104,940 LOC) is actually ported โ€” ~205 lines of transactional verbs plus ~830 lines of cited beforeSave hooks. The win is delivery/definition, not a full re-implementation of the transactional server.
  • Bonsai still wins on authoring. Manual IFC entity creation, arbitrary section planes, full PBR rendering, BCF export, structural/energy links โ€” the 52,267-line number is the logic footprint for what's actually built (extract/view/query), not a claim of replacing Bonsai's authoring surface.
  • Neither number counts the engine underneath. The browser's JS engine is pre-installed and free for us; Blender's C++ core is pre-installed and free for Bonsai. This compares add-on/application logic to add-on/application logic, not silicon to silicon.
  • LOC is a proxy, not a verdict. Every count came from a real clone or checkout and a real wc -l, dated and sourced below โ€” but fewer lines isn't automatically better lines.

Sources. iDempiere โ€” 1,427,147 Java LOC / 4,465 files, ~/idempiere-dev-setup/idempiere, measured 2026-06-08. Our ERP fold โ€” 55,283 LOC / 297 files, dedup union of bim-ootb:erp/ (origin/main) + bim-compiler:build/erp-only files, non-lib non-min JS, measured 2026-08-07. Bonsai โ€” 235,365 Python LOC / 652 files, src/bonsai/, github.com/IfcOpenShell/IfcOpenShell@v0.8.0, measured 2026-08-07 via sparse clone + wc -l. Our CAD fold โ€” 52,267 LOC / 341 files, bim-ootb:modeller/+common/+hr_bim_asset/+geomapping/ (origin/main), measured 2026-08-07. MOrder.completeIt() compare โ€” Migrate & Compare, witness W-FOLD-COMPLETE. Bonsai gap analysis โ€” internal/BonsaiGapAnalysis.md, prepared 2026-05-01. Not feature parity โ€” delivery-and-definition size, measured and re-run-able, not a demo number.