What is actually inside a SolidWorks file

A .sldprt is usually described as a closed box holding a Parasolid solid nobody else can read. Half of that is true. Here is what is really in the file, including the mesh that is in there all along.

Ask anywhere what is inside a SolidWorks part file and you get a consistent answer: a Parasolid solid, a commercial kernel format, closed, and if you want the geometry you export STEP. That answer is half right, and the half it gets wrong is the half that matters if you have a .sldprt in front of you and no SolidWorks licence.

Two containers, neither documented

A part written by SolidWorks 2014 or earlier is a Microsoft compound document: the same OLE2 structure as a .doc or a .xls, with a FAT, a directory laid out as a red-black tree, and a separate mini-stream for anything under 4096 bytes. Microsoft publishes that layout as MS-CFB, so opening one is a solved problem, and a reader written for .max files works on a .sldprt without modification.

Files from 2015 onwards are not compound documents. There is no signature at offset zero, no directory, and running strings over one gives you nothing. What is there is a repeated six-byte marker, 14 00 06 00 08 00, and after each one a header of four 32-bit words followed by a name and a payload. The payload is a bare deflate block, which is easy enough to spot. The name is where it gets strange.

The names are stored with the two nibbles of every byte swapped. The stream called Contents is written 34 f6 f4 74 56 f4 27 37: 0x34 becomes 0x43, which is C; 0xf6 becomes 0x6f, which is o; and so on. Undo that and the file turns from noise into a directory listing. It is not encryption and it is not compression. It is just enough obfuscation to stop a casual grep, which is presumably the point.

What the streams contain

A small part, a 0.64 mm stainless tube, has thirty streams. The interesting ones are:

StreamHolds
Contents/Config-0-PartitionThe solid, as a Parasolid XT transmit stream
Contents/DisplayListsA cached tessellation of that solid
Contents/DefinitionThe feature tree
PreviewPNGThe thumbnail Explorer shows
docProps/*.xmlAuthor, title, custom properties

The Parasolid stream really is closed, and not because nobody has looked at it. XT is self-describing enough to parse, but parsing it gets you trimmed NURBS surfaces and the curves that bound them. Turning those into triangles is a geometry kernel, not a file reader: you need surface evaluation, curve intersection and a meshing strategy that respects tolerance. That is a multi-year project and it is why every open-source effort stops here.

The display list is a different story.

The mesh that is already in the file

SolidWorks can open an assembly in lightweight mode and draw its components without rebuilding them. It can do that because it saves what it drew last time. That cache is Contents/DisplayLists, and it is a mesh.

The stream is an MFC-serialised object graph, the sort where each class writes its name once and is referred to by index afterwards. Reimplementing that is unnecessary, because the arrays inside it are self-describing. Each is framed as four 32-bit words followed by its bytes:

[u32 stride][u32 kind][u32 flag][u32 count]  then stride * count bytes

kind 100  floats      stride 12 = xyz triples, stride 8 = uv pairs
kind 8    integers    stride 4  = u32
kind 2    integers    stride 8  = u64

Walk the stream looking for headers that fit inside their own stream and you recover every array without touching the object graph at all. A face, serialised as a class called uoTempFaceTessData_c, is a run of them: a u32 array of triangle-strip lengths, then float32 positions in metres, then float32 unit normals, and sometimes texture coordinates.

Two details in there are easy to get wrong, and both produce geometry that looks almost right.

The first is the strip lengths. It is tempting to treat a face's positions as a single triangle strip, and for a simple face that works. But a face is very often several strips laid end to end in the same array, and stripping across the boundary sews the last corner of one strip to the first corner of the next. On a real part that produces long thin triangles spanning holes. In the sample corpus, 99 percent of faces carry a strip table and about a third of those have more than one strip, so this is the common case, not the edge case.

The second is subtler. A planar face's normals are all identical, which means an array of them looks exactly like a positions array followed by something unit-length. Match on that and you get phantom faces of zero extent sitting a metre from the origin, which is enough to wreck the bounding box and shrink the real model to a dot. Rejecting any candidate whose positions have zero extent removes them.

Checking it against the file's own opinion

Reverse engineering has an obvious failure mode: you get something plausible and believe it. SolidWorks solves this for you, because every file embeds the preview image SolidWorks itself rendered. Decode the mesh, render it from the same angle, and compare against the picture the file carries of itself.

Across 52 real parts, assemblies and drawings, 46 give geometry. A small obround block comes out with the counterbore and the small through hole its preview draws, in the same orientation. A McMaster 2-56 cap screw comes out with its thread and knurled head. A 2.7 MB part from the older container gives 809 faces and 17,040 triangles.

Of the six that give nothing, three are drawings with no cached model, and three are pre-2015 files whose display list uses a compression that is neither deflate nor the zlib block chain the rest of that era uses. That one is unsolved: it is not deflate at any byte or bit offset, and it is not an LZSS in any of the shapes worth brute forcing. Opening those in a newer SolidWorks and saving rewrites them into the container that reads.

What this is and is not

The mesh you get is the tessellation SolidWorks drew with, at whatever deviation the part was saved at. For looking at a part, printing it, putting it in a game engine or measuring it approximately, that is exactly what you want. For machining from it, or for any operation that needs the analytic surface rather than a triangulation of it, it is not: export STEP and use that.

It also means a file saved with graphics suppressed, or one only ever rebuilt headlessly, has no cache and gives nothing. That is worth saying out loud rather than failing generically, which is what the reader here does.

You can try it on your own files: the viewer opens a .sldprt, .sldasm or .slddrw with its faces in the tree, and the SLDPRT to GLB converter writes one out. Both run entirely in the browser, which for a CAD file that may be under NDA is not a small detail.

Sources

Primary references for the claims above. Where a specification exists, it is cited in preference to anybody's summary of it.

  1. [MS-CFB]: Compound File Binary File Format, Microsoft Open Specifications
    The container SolidWorks used up to 2014, and the FAT, DIFAT and directory layout this reader implements.
  2. Parasolid, Siemens Digital Industries Software
    The geometry kernel whose XT transmit format holds the exact solid.
  3. openswx, GitHub
    A dedicated C++ SolidWorks reader; useful for confirming where other implementations stop.
  4. RFC 1951: DEFLATE Compressed Data Format Specification, IETF
    Both SolidWorks containers store their streams as deflate, one wrapped and one bare.
  5. Open-source hardware for chronic tetrode drives, Wilson Lab, MIT
    The published SolidWorks parts and assemblies this reader was tested against.

Formats in this post

More from the blog