01 Overview
D3Guard is a Blender add-on for designing dental occlusal splints and night-guards β it ingests upper and lower arch scans, lets the clinician mark landmarks, builds a virtual articulator, animates jaw motion, generates the functional occlusal surface from that motion, and exports a printable splint. It was originally co-authored with Patrick R. Moore for Blender 2.79b around 2017, and has been in production use in my own workflow since. This project is the work of bringing it forward: a 30-day version port from Blender 2.79 to 4.5 LTS, a follow-on arc that replaced its vendored in-viewport UI framework with an in-house one, and a cleanup that removed ~220,000 lines of dead code. Throughout, one rule held: replicate the 2.79 behavior exactly β port, don't rewrite.
2.79 β 4.5
166 registered operators moved across eight major Blender version breaks and Python 3.5 β 3.11, with the layers that touch Blender internals rewritten: bgl β the gpu module, and an OpenVDB CSG port.
d3ui
An in-house, tested, GPU-native widget-and-modal framework built to replace ~10 vendored copies of the inherited viewport UI, with public API kept parity so callers migrate by changing import lines.
Dead-code audit
An import-graph liveness trace that found whole subtrees with zero real importers, then removed ~415 dead files / ~220,000 lines β each with a documented recovery path.
02 Why I built it
Blender 2.79b was released in 2017 and has been unsupported for years; continuing to maintain software this codebase's users depend on, on top of an unsupported engine, wasn't sustainable. Blender 4.5 LTS is a long-term-support release with maintenance patches promised into 2027 and beyond, so the port buys the add-on a stable base and unblocks the rest of the D3Tool product family that builds on it.
The second arc had a more specific motive. The inherited codebase was structurally inverted β roughly ten copies of the vendored CookieCutter UI framework, twenty-three copies of a compatibility shim, and several copies of a shared support library. The duplication wasn't harmless: the mere existence of the copies kept causing fixes to land on a dead subtree instead of the live one. Replacing them with a single tested framework was as much about making the tree honest as it was about the UI.
03 What I built & how it works
A desktop Blender add-on β no server, no port, no auth. It runs inside Blender; the modernization is in the layers underneath the clinical workflow.
Fig. 1 β the clinical workflow is preserved from 2.79; the internals-facing layers (drawing, geometry CSG, UI) were rewritten around it.
The method mattered more than any single fix. A read-only copy of the original 2.79 add-on served as the oracle, and every claim that the port matched the original had to cite a specific file and line in it. The approach ran in five moves:
- Freeze a 2.79 oracle β a separate read-only copy of the original add-on; every "this matches 2.79" claim cites a
file:linein it. - Research before fixing β line-by-line traces of the original behavior (13 research reports) before any non-trivial change.
- Port, not rewrite β replicate 2.79 behavior exactly, preserving even harmless upstream quirks rather than "improving" them.
- Rewrite only the internals-facing layers β immediate-mode drawing β the
gpumodule, removed OpenVDB APIs re-expressed as level-set CSG, depsgraph and operator-lifecycle changes. - Verify end-to-end β run the full 24-step clinical workflow to completion at each milestone, not just unit checks.
04 π Skills & tech used
No ML/AI in this project β the AI work lives in sibling repos; listed here only to be complete.
05 Notable challenges & decisions
Most of the hard parts were diagnosis, not typing β one wrong character, one wrong bulk-replace, one framework built to stop a whole class of mistakes.
A single * that looked like three broken subsystems
The mounting matrix was built with T*R_fox instead of T@R_fox β element-wise multiply silently zeroed the translation. It surfaced as three seemingly independent failures (the pin operator, the functional surface, and the boolean subtraction), and four wrong pin-operator designs were tried before the real cause was found upstream. The lesson I wrote down: when several subsystems all look broken at once, suspect one upstream positioning error, not three bugs.
Building d3ui because the duplication itself was the bug
The inherited viewport UI was roughly ten copies of a vendored framework, and fixes kept landing on a dead copy instead of the live one. Rather than re-vendor, I built an in-house retained-mode GPU widget-and-modal framework whose public surface intentionally mirrors the old one 1:1, so callers migrate by changing import lines only. It ships with 432 unit tests, 4 pixel-level visual-regression tests, and 20 in-Blender integration tests.
Deleting ~220,000 lines β only after proving they were dead
Three subtrees everyone believed were live turned out to have zero real importers; they "only looked alive because their own bundled support library self-imported." An import-graph audit with shadowed-name detection separated the live tree from the dead one, and ~415 files / ~220,000 lines came out β each subtree with a documented recovery path in case the call was wrong.
StructRNA freed the operator out from under itself
Blender 4.5 frees a dispatched operator's StructRNA between __init__ and execute, so every self access fails β including declared properties. Three strategies failed before a working fix: pass parameters through module-level state and let a long-lived director operator absorb the polling, instead of relying on the freed instance.
.hide β hide_viewport was the trap, not the fix
The obvious sweep β and the linter's own suggestion β was to replace the old .hide with hide_viewport. But hide_viewport removes objects from the depsgraph, which quietly breaks constraints and ray-casts; the correct call is hide_set(). About 500 sites had to be re-swept once the difference was understood.
Port-not-rewrite went all the way down. Harmless upstream quirks were kept verbatim rather than "cleaned up," and a viewport visibility change that read as an improvement was deliberately reverted to match the original 2.79 behavior β fidelity to a known-good baseline mattered more than a nicer diff.
06 Results
Sources: the 14,500-word port report and three delta reports, the d3ui test-status doc, and git history (94 commits on master).
07 Screenshots & video
No live demo β it's a desktop Blender add-on. The clearest record is a screen recording of the full workflow; captures below are placeholders.
full 24-step Michigan workflow on a sample scan
2.79 vs 4.5 Β· same scan, same steps
two floating GPU-drawn windows
08 Honest status
The core version port ran roughly 30 days in spring 2026; the d3ui framework replacement and the dead-code cleanup followed through early May, and the repository has been quiet since as work moved to sibling projects. This page is a write-up with placeholder media β the screenshots and video above are not yet captured.
A few honest caveats. The add-on carries its own disclaimer: it is "a training and educational Dental Design CAD Tool not intended for clinical use." A full clinical-workflow session was run end to end with zero tracebacks, but I'm describing that as a clean run β not asserting external clinical validation, and it isn't independently confirmed who ran it. Only Windows x64 has been tested (Mac and Linux binaries ship but are unverified), and a handful of low-priority TODOs and known latent dead files remain documented rather than hidden. The original 2.79 add-on was co-authored with Patrick R. Moore; the work described here is the modernization.