altium.js is a JavaScript library for parsing and rendering Altium schematic (SchDoc) files in the browser. It currently handles most features of schematic documents as of Altium Designer version 22.5.
To take altium.js for a test drive, open altium_sch.html
in a browser. A demo document will be opened and displayed. Click the "Choose File" button at the top to load a SchDoc file. It will be rendered below. If you scroll past the preview, you'll also find a CSV-compatible bill of materials (BoM).
Altium SchDoc files are OLE Compound Documents. The main stream within the document is a sequence of records, each of which comprises an ASCII/UTF8 encoded set of properties. You can read more about the internal format and record types in the python-altium documentation.
The altium_sch.html
page creates a global variable called altiumDocument
, which is an AltiumDocument
object. It has the following properties:
objects
- an array of objects in the document, each typed to a specific class (e.g.AltiumComponent
,AltiumWire
,AltiumParameter
, etc.), with each class being a subclass ofAltiumObject
. this is the main output of the parser, and is most likely where you want to start querying for things.sheet
- provides convenient access to the sheet object for the document.records
- an array of record entries extracted from the underlying file format. each record entry is processed into an object by the parser. records refer to each other by their indices (starting at 0, which is usually a sheet record).record_object_lookup
- a mapping object from record indices to their resultant objects. you can use this to take a record index and find the object that was created from it. eachAltiumObject
has arecord_index
property that acts as the inverse mapping, and you can also access the record directly (without needing to do a secondary lookup) using the object'ssource_record
property.stream
- a stream object that represents the underlying bytes of the schematic data. the schematic data stream is extracted from the SchDoc file, which is an OLE compound document.
Each AltiumObject
has a parent_object
field and a child_objects
field to help traverse the document hierarchy.
You can run these queries in your browser console. This section should also be useful reference if you want to roll your own tooling using this library.
The following query finds all components with a design item ID of "AO3401A":
altiumDocument.objects.filter(
o => o instanceof AltiumComponent &&
o.design_item_id == "AO3401A")
You can also search by description, for example if you wanted to find all MOSFETs:
altiumDocument.objects.filter(
o => o instanceof AltiumComponent &&
o.description.includes("MOSFET"))
Designators and components are separate objects, and there is some complexity to how they work.
You can search for a designator by its text as follows:
altiumDocument.objects.filter(
o => o instanceof AltiumDesignator && #
o.text == "U4")
However, if U4 is a multi-part component (e.g. an LM324 with four separate opamp parts) this query will return separate designator objects for each part, all with the text "U4". This is because the suffix letters (U4A, U4B, etc.) are added by Altium at runtime based on the associated component's current part ID. A helper property, full_designator
, is included to get around this inconvenience:
altiumDocument.objects.filter(
o => o instanceof AltiumDesignator &&
o.full_designator == "U4A")
The parent object of the designator is usually an AltiumComponent
, so you can use the parent_object
property to find parts with the exact specified designator:
altiumDocument.objects.filter(
o => o instanceof AltiumDesignator &&
o.full_designator == "U4A"
).map(d => d.parent_object)
However, due to implementation complexities and variances across Altium versions, there may be additional mapping objects sat between the current object and the parent object you want to find. In such a case, the direct parent object of the AltiumDesignator
might not be an AltiumComponent
, but something else. To make things more reliable, you can use the find_parent()
helper function on any AltiumObject
in order to find a parent of a specific type:
altiumDocument.objects.filter(
o => o instanceof AltiumDesignator &&
o.full_designator == "R3"
).map(d => d.find_parent(AltiumComponent))
Footprints, simulation models, and signal integrity models are all represented internally by "implementations". All implementations are saved in the file regardless of whether they are the currently active ones, so if you've got a generic resistor part with footprints for 0603, 0805, etc. then there will be separate AltiumImplementation
objects for each of those. You can tell whether an implementation is actively selected by checking its is_current
property.
Each AltiumImplementation
is typically a child of an AltiumImplementationList
object, which itself is typically a child of an AltiumComponent
. The find_parent()
helper is useful for traversing the hierarchy here.
For example, this query finds all components that have a SOT23 footprint currently selected:
altiumDocument.objects.filter(
o => o instanceof AltiumImplementation &&
o.is_footprint && o.is_current &&
o.model_name.includes("SOT23")
).map(f => f.find_parent(AltiumComponent))
You can search for power ports by name as follows:
altiumDocument.objects.filter(
o => o instanceof AltiumPowerPort &&
o.text == "VCC")
You can also search by the type of power port:
altiumDocument.objects.filter(
o => o instanceof AltiumPowerPort &&
o.style_name == "POWER_GND")
The defined power port names are ARROW, BAR, WAVE, POWER_GND, SIGNAL_GND, EARTH, GOST_ARROW, GOST_POWER_GND, GOST_EARTH, and GOST_BAR. A value of DEFAULT is used to represent two possible port types: when is_off_sheet_connector
is false it represents a circle port, and when it is true it represents an off-sheet connector.
The parser handles a lot of the translation from underlying attributes into a clean format, but, for the most part, only the properties that were necessary to implement the renderer are actually parsed out to a high-level value. You can still access all of the underlying attributes, though, through the attributes
and raw_attributes
fields on every AltiumObject
. If you can't find the value you're looking for directly in an object, check the attributes field.
The attributes
field is an object built from all of the underlying attributes, with special characters in the name replaced with underscores. You can also access the raw attributes as an array of objects with name
and value
fields. All values are strings.
For example, if I wanted to know whether the sheet border is enabled, I could check the following value:
altiumDocument.sheet.attributes.borderon
This will either be "T" for true, or "F" for false. It could also be missing, since many fields are not added to documents if the field value is the default.
The altium_sch_document.js
script contains the parser, which you invoke using the AltiumDocument
class constructor. It needs access to the data from inside the "FileHeader" stream of the OLE Compound Document file. A rudimentary OLE parser is included in ole.js
, and several helper scripts are also required (base64_binary.js
, helper_extensions.js
, and u8stream.js
).
Given a SchDoc file as an ArrayBuffer
object, you can invoke the parser as follows:
// parse the ArrayBuffer as an OLE Compound Document
let ole = new OLE(data);
// find the FileHeader stream
let fileHeader = ole.directory_entries.find(de => de.name == "FileHeader");
// read the result as a U8Stream (a wrapper around Uint8Array)
let fileHeaderData = fileHeader.read_all();
// parse it as an Altium document
let altiumDocument = new AltiumDocument(fileHeaderData);
The altium_sch_renderer.js
script takes a document and renders it to a canvas object:
let renderer = new AltiumSchematicRenderer(canvas, altiumDocument);
renderer.render();
You can take a look at altium_sch.html
for further reference.
There are some known issues.
Parser:
- The OLE parser is very rudimentary and only supports reading data from a single contiguous stream. My understanding of OLE Compound Document formats is limited, but it is possible that very large SchDoc files (55MB+) will not load properly due to exceeding 110 sectors in size.
Renderer:
- Buses and bus entries aren't drawn.
- Ports (regular ones, not power ports) aren't drawn.
- "No ERC" blocks and markers aren't drawn.
- Sheet symbols and sheet entries aren't drawn.
- Sheet information blocks aren't drawn.
- Vertical alignment of text in text frames does not account for extra lines caused by word wrap.
- Alignment of rotated text is sometimes a bit off, and vertical text may be horizontally flipped in some cases compared to Altium.
- Only power bars, power ground, and earth symbols are supported for power ports. All others are just drawn as a box.
- Power port symbols are always drawn in their "natural" orientation, i.e. ground facing down and power bars facing up.
If you want to open a PR to fix any of these, that'd be appreciated.
altium.js and its components are released under MIT License.
The base64_binary.js
file was developed by Daniel Guerrero. See the file contents for software licensing information.
Special thanks to Martin Panter, who authored the python-altium library and saved me a lot of reverse engineering work.