DEV Community

Cover image for OpenSKP 1.1.0: A Native .skp Writer, Now in All Five Languages
Ahsan Mehmood
Ahsan Mehmood

Posted on

OpenSKP 1.1.0: A Native .skp Writer, Now in All Five Languages

Until 1.1.0, OpenSKP was a read path. You could parse a .skp file — either container format, all five languages — and get materials, layers, geometry, and metadata out of it. What you couldn't do was make a new one. 1.1.0 closes that gap: a real writer, producing files SketchUp itself opens without complaint, shipped in Python, TypeScript, .NET, Dart, and C++ at the same time.

Why write support is a different problem than read support

Parsing forgives sloppiness in a way writing doesn't. A parser that mishandles an obscure edge case just gets slightly wrong data for that one field — annoying, but survivable, and often invisible unless you're specifically checking. A writer that gets the container format wrong produces a file that SketchUp refuses to open at all, or worse, opens with silently corrupted geometry. There's no partial credit.

That asymmetry shaped how the writer got built. It targets the modern VFF container exclusively — the ZIP-based, TLV-tree format used by SketchUp 2021 and later — rather than also targeting the legacy MFC stream format, because VFF is what every current SketchUp install expects by default and where new files should live going forward. Every writer feature was validated the same way the parser's correctness was validated originally: produce a file, open it with the real SketchUp SDK, and confirm the SDK's own reading of geometry, materials, and layer assignments matches what was intended, field by field — not just "the file has a size greater than zero and doesn't crash on open."

What the writer actually does

The API surface is intentionally generic rather than shaped around specific object types. There's no Chair class or Table builder baked into the library — you compose scenes out of the same primitives SketchUp itself works with:

  • Materials — solid colors, opacity, and image textures
  • Layers (tags) — for organizing geometry the way SketchUp's own Tags panel does
  • Groups and component definitions — the same grouping/instancing model SketchUp uses natively, including nested groups
  • Curves and faces — arbitrary polygon geometry with material and layer assignment per face
  • create() for a brand-new file, and open_existing() for loading a file, editing it, and writing it back out

open_existing() in particular was the harder of the two entry points to get right. It isn't a byte-patcher that finds the one chunk you touched and surgically rewrites it in place — that approach is fragile against any structural change (add a face, and every downstream offset in the container shifts). Instead it does a full parse of the existing file into the same in-memory model the writer already knows how to serialize, applies your edits to that model, and replays the entire thing back out through the same write path a brand-new file goes through. Slower than a patch, but correct by construction: if create() is trustworthy, open_existing() inherits that trust instead of needing its own separate proof.

Five ports, five different sets of bugs

The writer shipped in Python first, since Python was already the most mature of the five ports and the fastest place to validate the design against the real SDK. Porting it to the other four languages wasn't a mechanical translation exercise — each port's own CI caught real, language-specific problems that the Python reference implementation simply couldn't have surfaced:

  • C++ — a check_writable helper failed to compile under the CI's stricter build flags, a class of error Python's dynamic typing has no equivalent to catching until runtime, if at all. The C++ port also turned up a clang-format issue where the CI's diff report was silently truncating on longer violations, which needed fixing in the tooling itself before it could be trusted to gate anything.
  • Dart — trigonometric rounding in transform-matrix math didn't match the other languages' output bit-for-bit, traced to a difference in how Dart's math library rounds versus Python's, and fixed by aligning the rounding step explicitly rather than relying on each language's default behavior.
  • Two of the ports hit test-ordering bugs — tests that passed individually but failed when run as part of the full suite, because they shared mutable fixture state that Python's test runner happened to isolate in a way the other runner didn't by default.

None of these were writer-logic bugs in the sense of "the geometry is wrong." They were the ordinary friction of porting real, non-trivial code across five different type systems, build toolchains, and test runners — exactly the kind of thing that's invisible if you only ever ship one language, and exactly why keeping five active ports is more expensive than it sounds, but catches more than a single-language project ever would.

Shipping five packages at once

1.1.0 went out to all five registries in the same release cycle — PyPI, npm, NuGet, pub.dev, and a tagged GitHub Release with prebuilt C++ artifacts (C++ has no package registry equivalent, so it ships as a downloadable tarball/zip pair instead). Coordinating that many release pipelines in one pass surfaced two more process-level snags worth naming honestly: a batched multi-language tag push needed the tags separated out per-language rather than pushed as one lump, and pub.dev's own publish flow has a gotcha around tag naming that isn't obvious until it rejects a push.

Neither was a code bug. Both are the kind of thing you only learn by actually running a five-language release end to end, which is exactly what 1.1.0 forced.

What's next

Read and write both exist now, but write support is currently VFF-only — no legacy-MFC writing, and no re-encoding a legacy file into the modern container. Conversion the other direction (glTF, IFC, OBJ into .skp) is on the roadmap but not started. If either of those is something you'd use, the issue tracker is open.


Install:

pip install openskp          # Python
npm install openskp          # TypeScript / JavaScript
dotnet add package OpenSKP   # .NET
dart pub add openskp         # Dart
Enter fullscreen mode Exit fullscreen mode

OpenSKP is open source under the MIT license: github.com/iamahsanmehmood/openskp

Top comments (0)