Reads 3Shape .dcm intraoral scan files in JavaScript and hands you the mesh.
Writes STL and PLY back out. No dependencies, no build step. Runs in Node and
in a browser.
A 3Shape .dcm is not a DICOM file. It shares the extension and nothing
else. Inside is an HPS packed scan: XML, with the vertices and triangles packed
into base64 blocks. This is why medical imaging tools refuse to open one.
Some scans are encrypted. This library reads those too. See Encrypted scans.
import { parse, toSTL, toPLY } from './parser.mjs';
const mesh = parse(arrayBuffer);
mesh.vertices // Float32Array, three floats per point
mesh.indices // Uint32Array, three per triangle
mesh.meta.schema // 'CA', 'CC' or 'CE'
mesh.meta.encrypted // true if the file was encrypted
writeFileSync('scan.stl', Buffer.from(toSTL(mesh.vertices, mesh.indices)));
writeFileSync('scan.ply', Buffer.from(toPLY(mesh.vertices, mesh.indices)));parse throws an HPSError with a code you can switch on: NOT_HPS,
UNSUPPORTED_SCHEMA, DECRYPT_FAILED, PARSE_ERROR, BAD_INPUT.
Needs Node 18 or newer. In a browser, import the same file and pass it the
ArrayBuffer from a FileReader.
| Schema | Encrypted | Status |
|---|---|---|
| CA | no | reads |
| CC | no | reads |
| CE | yes | reads, decrypts |
| CB | no | not supported |
Triangles are stored as a small opcode program, not a plain list. The parser runs that program. All eleven opcodes are handled, for both 16 bit and 32 bit index files. Vertex colors are read when the file has them.
CE files hide the vertex coordinates behind Blowfish. The triangles stay in the clear. The key is built from values inside the file itself, so no server call is needed:
- Start with a fixed 16 byte base key.
- If the file has
EKIDof1and aPackageLockList, take the MD5 of that list in canonical form and append its 32 hex digits as ASCII. The key is now 48 bytes. - Decrypt the vertex block with Blowfish in ECB mode. No IV. Each 8 byte block is two 32 bit words, little endian.
- Pad with zeros to a block boundary, then cut back to
vertexCount * 12.
Canonical form of the lock list means: split on ;, drop the empty parts, sort
them, remove repeats, join back with a trailing ;.
Every file carries an Adler-32 of its own plaintext vertex bytes. The parser
checks it. If the check fails you get DECRYPT_FAILED instead of a garbage
mesh.
Some scans use a key that lives on 3Shape's Key Exchange service. Those cannot
be opened offline by anything. They fail the checksum and report
DECRYPT_FAILED.
crypto.mjs has its own MD5, Blowfish and Adler-32 so the whole thing works in
a browser tab. Both are tested against the published RFC and Blowfish test
vectors, and against OpenSSL at five key lengths.
The Open3SDCM README describes CBC mode with PKCS#7 padding and a truncated
MD5. Its own source code does not do that. The steps above match the source,
and they match real files. Follow the code, not the README.
node test.mjsFour real scans, two of them encrypted. Every coordinate is compared against a reference mesh produced by a different tool, so a wrong answer cannot pass. Last run: 4 of 4 decoded, all coordinates exact.
parser.mjs the reader, plus the STL and PLY writers
crypto.mjs MD5, Blowfish, Adler-32, CE key derivation
test.mjs the test run
samples/ real scans and their reference meshes
The format work rests on two projects, and the sample files come from them:
See samples/README.md for what each file is and which license covers it.
If you want to open a scan without writing code, ScanLoupe uses this parser in the browser. The file stays on your machine.
MIT