<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bubu Dong</title>
    <description>The latest articles on DEV Community by Bubu Dong (@bubudong).</description>
    <link>https://dev.to/bubudong</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3769947%2F9df67d90-2e18-4692-88c5-3935e5bc82dc.png</url>
      <title>DEV Community: Bubu Dong</title>
      <link>https://dev.to/bubudong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bubudong"/>
    <language>en</language>
    <item>
      <title>Why PrusaSlicer can't open your Bambu 3MF — and how I flattened the 3MF production extension in the browser</title>
      <dc:creator>Bubu Dong</dc:creator>
      <pubDate>Wed, 27 May 2026 07:05:22 +0000</pubDate>
      <link>https://dev.to/bubudong/why-prusaslicer-cant-open-your-bambu-3mf-and-how-i-flattened-the-3mf-production-extension-in-the-3p36</link>
      <guid>https://dev.to/bubudong/why-prusaslicer-cant-open-your-bambu-3mf-and-how-i-flattened-the-3mf-production-extension-in-the-3p36</guid>
      <description>&lt;p&gt;You find a great model on MakerWorld, download the &lt;code&gt;.3mf&lt;/code&gt;, open PrusaSlicer to tweak it... and PrusaSlicer just won't load it. No mesh, or an error, or an empty plate. The file isn't corrupt — Bambu Studio opens it fine. So what's going on?&lt;/p&gt;

&lt;p&gt;I went down this rabbit hole after a real bug report on a PrusaSlicer GitHub issue, fixed it, and the fix runs entirely in the browser. Here's the anatomy of the problem and how the flattening works.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 3MF is just a zip
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.3mf&lt;/code&gt; is a zip archive. Rename one to &lt;code&gt;.zip&lt;/code&gt;, unzip it, and a simple one looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3D/3dmodel.model        ← the geometry (XML)
[Content_Types].xml
_rels/.rels
Metadata/...            ← thumbnails, slicer settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;3dmodel.model&lt;/code&gt; holds &lt;code&gt;&amp;lt;vertices&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;triangles&amp;gt;&lt;/code&gt; inside &lt;code&gt;&amp;lt;resources&amp;gt;&lt;/code&gt;, and a &lt;code&gt;&amp;lt;build&amp;gt;&lt;/code&gt; block that places those objects on the plate. Any slicer that speaks vanilla 3MF reads this and you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bambu uses the "production extension"
&lt;/h2&gt;

&lt;p&gt;Open a Bambu Studio 3MF and the root model looks very different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;model&lt;/span&gt; &lt;span class="na"&gt;requiredextensions=&lt;/span&gt;&lt;span class="s"&gt;"p"&lt;/span&gt; &lt;span class="na"&gt;xmlns:p=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/3dmanufacturing/production/2015/06"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;resources&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;object&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;p:UUID=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;components&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;p:path=&lt;/span&gt;&lt;span class="s"&gt;"/3D/Objects/object_1.model"&lt;/span&gt; &lt;span class="na"&gt;objectid=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;p:UUID=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;component&lt;/span&gt; &lt;span class="na"&gt;p:path=&lt;/span&gt;&lt;span class="s"&gt;"/3D/Objects/object_2.model"&lt;/span&gt; &lt;span class="na"&gt;objectid=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;p:UUID=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        ...
      &lt;span class="nt"&gt;&amp;lt;/components&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/resources&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/model&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: &lt;strong&gt;the root model has no geometry of its own.&lt;/strong&gt; It declares &lt;code&gt;requiredextensions="p"&lt;/code&gt; (the 3MF &lt;em&gt;production extension&lt;/em&gt;) and points at a pile of external part files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3D/3dmodel.model               ← no meshes, just &amp;lt;component p:path=...&amp;gt; references
3D/Objects/object_1.model      ← actual &amp;lt;vertices&amp;gt;/&amp;lt;triangles&amp;gt;
3D/Objects/object_2.model
... (one real file had 25 of these)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production extension exists to split big assemblies across files and stream them. Bambu Studio leans on it heavily. The catch: &lt;strong&gt;PrusaSlicer doesn't implement the production extension.&lt;/strong&gt; It sees &lt;code&gt;requiredextensions="p"&lt;/code&gt;, sees a root model with no geometry, and gives up. That's the whole bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: flatten it back to vanilla 3MF
&lt;/h2&gt;

&lt;p&gt;The goal is to produce a single self-contained &lt;code&gt;3dmodel.model&lt;/code&gt; with all geometry inlined, no &lt;code&gt;p:&lt;/code&gt; namespace, no external parts — the boring kind of 3MF every slicer reads. Sounds simple; the fiddly part is the ID namespaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each part file has its own local &lt;code&gt;object id&lt;/code&gt; space.&lt;/strong&gt; A &lt;code&gt;&amp;lt;component objectid="1"&amp;gt;&lt;/code&gt; inside the root refers to id &lt;code&gt;1&lt;/code&gt; &lt;em&gt;in the file named by &lt;code&gt;p:path&lt;/code&gt;&lt;/em&gt;, not id &lt;code&gt;1&lt;/code&gt; in the root. So you can't just concatenate — you have to remap.&lt;/p&gt;

&lt;p&gt;The flattening steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Assign globally-unique object ids&lt;/strong&gt; across every &lt;code&gt;.model&lt;/code&gt; file in the archive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline external meshes in dependency order&lt;/strong&gt; — topological sort so leaf meshes come before the assemblies that reference them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite every reference&lt;/strong&gt;: each &lt;code&gt;p:path&lt;/code&gt; + &lt;code&gt;objectid&lt;/code&gt; pair, same-file &lt;code&gt;&amp;lt;component&amp;gt;&lt;/code&gt; references, and the &lt;code&gt;&amp;lt;build&amp;gt;&lt;/code&gt; item &lt;code&gt;objectid&lt;/code&gt;s, all repointed to the new global ids.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip the extension&lt;/strong&gt;: drop &lt;code&gt;requiredextensions&lt;/code&gt;, the production (&lt;code&gt;p&lt;/code&gt;) namespace, and Bambu's vendor namespace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit a clean container&lt;/strong&gt;: a minimal &lt;code&gt;[Content_Types].xml&lt;/code&gt; and &lt;code&gt;_rels/.rels&lt;/code&gt; (this also fixes dangling thumbnail relationships).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why I did it with string ops, not a DOM parser
&lt;/h2&gt;

&lt;p&gt;The transform only touches &lt;em&gt;structural&lt;/em&gt; tags — &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;component&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;build&amp;gt;&lt;/code&gt;, the namespace declarations. It never needs to read or rewrite the &lt;code&gt;&amp;lt;vertex&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;triangle&amp;gt;&lt;/code&gt; bodies, which are the huge part.&lt;/p&gt;

&lt;p&gt;So instead of pulling in a DOM/XML parser (heavy, and browser/Node behave differently), it's careful string manipulation over the structural elements. Two payoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs &lt;strong&gt;identically in Node and the browser&lt;/strong&gt;, so I could validate the exact same code against a real file on the command line.&lt;/li&gt;
&lt;li&gt;It's fast and memory-light even on multi-megabyte files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Did it actually work?
&lt;/h2&gt;

&lt;p&gt;The bug report came with a real 6.3 MB file from MakerWorld — 25 external object models. After flattening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;151,317 vertices&lt;/strong&gt; in → &lt;strong&gt;151,317&lt;/strong&gt; out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;302,542 triangles&lt;/strong&gt; in → &lt;strong&gt;302,542&lt;/strong&gt; out&lt;/li&gt;
&lt;li&gt;all 25 external files merged into one root model&lt;/li&gt;
&lt;li&gt;every reference resolved, no forward references, XML well-formed&lt;/li&gt;
&lt;li&gt;zero production-extension residue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the actual test: the flattened file &lt;strong&gt;opens in PrusaSlicer.&lt;/strong&gt; Same geometry, now as a vanilla 3MF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / steal it
&lt;/h2&gt;

&lt;p&gt;I put this in a free, browser-only tool — drop a Bambu &lt;code&gt;.3mf&lt;/code&gt;, get a Prusa-friendly &lt;code&gt;.3mf&lt;/code&gt; back, nothing uploaded: &lt;strong&gt;&lt;a href="https://flip3d.app/tools/bambu-3mf-to-prusa/" rel="noopener noreferrer"&gt;flip3d.app/tools/bambu-3mf-to-prusa&lt;/a&gt;&lt;/strong&gt;. It's part of &lt;a href="https://flip3d.app" rel="noopener noreferrer"&gt;Flip3D&lt;/a&gt;, a set of 3D file tools that all run client-side.&lt;/p&gt;

&lt;p&gt;It's open source if you want to read the flattener or file an issue: &lt;strong&gt;&lt;a href="https://github.com/suyfdong/flip3d" rel="noopener noreferrer"&gt;github.com/suyfdong/flip3d&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you've hit the "PrusaSlicer won't open my Bambu file" wall, that's why — and now you know exactly what's happening under the zip.&lt;/p&gt;

</description>
      <category>3dprinting</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built free loan payoff calculators to help people save on interest</title>
      <dc:creator>Bubu Dong</dc:creator>
      <pubDate>Fri, 13 Feb 2026 02:45:09 +0000</pubDate>
      <link>https://dev.to/bubudong/i-built-free-loan-payoff-calculators-to-help-people-save-on-interest-3947</link>
      <guid>https://dev.to/bubudong/i-built-free-loan-payoff-calculators-to-help-people-save-on-interest-3947</guid>
      <description>&lt;p&gt;I recently shipped a side project: &lt;a href="https://payoffcalculators.org" rel="noopener noreferrer"&gt;PayoffCalculators.org&lt;/a&gt; — a set of free online loan payoff calculators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Most mortgage calculators only show you the monthly payment. They don't answer the real question: &lt;strong&gt;how much can I save by paying extra?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted a simple tool where you plug in your loan details, add an extra monthly payment, and instantly see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many months you cut off your loan&lt;/li&gt;
&lt;li&gt;How much interest you save&lt;/li&gt;
&lt;li&gt;A full amortization schedule&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's included
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/mortgage-payoff-calculator" rel="noopener noreferrer"&gt;Mortgage Payoff Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/auto-loan-payoff-calculator" rel="noopener noreferrer"&gt;Auto Loan Payoff Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/loan-payoff-calculator" rel="noopener noreferrer"&gt;Loan Payoff Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/rv-loan-payoff-calculator" rel="noopener noreferrer"&gt;RV Loan Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/motorcycle-loan-payoff-calculator" rel="noopener noreferrer"&gt;Motorcycle Loan Calculator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://payoffcalculators.org/construction-loan-calculator" rel="noopener noreferrer"&gt;Construction Loan Calculator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;p&gt;Next.js + Tailwind CSS, deployed on Cloudflare Workers. The calculation formulas are open source:&lt;br&gt;
&lt;a href="https://github.com/suyfdong/loan-payoff-formulas" rel="noopener noreferrer"&gt;loan-payoff-formulas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No signup, no ads, no tracking. Just math.&lt;/p&gt;

&lt;p&gt;Would love any feedback!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
